/////////////////////////////////////////////////////////////////////////////
//
// CListCtrl - A WTL list control with Windows Vista style item selection.
//
// Revision:      1.5
// Last modified: 9th April 2006
//
/////////////////////////////////////////////////////////////////////////////

#pragma once

#include <set>
#include <algorithm>
#include <atltheme.h>
#include <atlctrlx.h>

#include "DragDrop.h"
#include "DropArrows.h"
#include "TitleTip.h"
#include "ListEdit.h"
#include "ListCombo.h"
#include "ListDate.h"

using namespace std;

struct CListColumn {
    CString m_strText;
    int m_nWidth;
    BOOL m_bFixed;
    UINT m_nFormat;
    UINT m_nFlags;
    int m_nImage;
    int m_nIndex;
    CListArray < CString > m_aComboList;
};

template < class T >
class CListImpl : public CWindowImpl< CListImpl< T > >,
    public CDoubleBufferImpl< CListImpl< T > >
{
public:
    CListImpl()
    {
        m_bShowHeader = TRUE;
        m_bShowThemed = TRUE;
        m_bSortAscending = TRUE;
        m_bButtonDown = FALSE;
        m_bMouseOver = FALSE;
        m_bColumnSizing = FALSE;
        m_bBeginSelect = FALSE;
        m_bSingleSelect = FALSE;
        m_bFocusSubItem = FALSE;
        m_bGroupSelect = FALSE;
        m_bEnableHorizScroll = FALSE;
        m_bEnableVertScroll = FALSE;
        m_bShowHorizScroll = TRUE;
        m_bShowVertScroll = TRUE;
        m_bShowSort = TRUE;
        m_bResizeTimer = FALSE;
        m_bDragDrop = FALSE;
        m_bSmoothScroll = TRUE;
        m_bEditItem = FALSE;
        m_bScrolling = FALSE;
        m_bScrollDown = FALSE;
        m_bTileBackground = FALSE;
        m_nMouseWheelScroll = 3;
        m_nTotalWidth = 0;
        m_nHeaderHeight = 0;
        m_nItemHeight = 0;
        m_nFirstSelected = NULL_ITEM;
        m_nFocusItem = NULL_ITEM;
        m_nFocusSubItem = NULL_SUBITEM;
        m_nHotItem = NULL_ITEM;
        m_nHotSubItem = NULL_SUBITEM;
        m_nTitleTipItem = NULL_ITEM;
        m_nTitleTipSubItem = NULL_SUBITEM;
        m_nSortColumn = NULL_COLUMN;
        m_nHighlightColumn = NULL_COLUMN;
        m_nDragColumn = NULL_COLUMN;
        m_nHotColumn = NULL_COLUMN;
        m_nHotDivider = NULL_COLUMN;
        m_nColumnSizing = NULL_COLUMN;
        m_nScrollOffset = 0;
        m_nScrollDelta = 0;
        m_nScrollUnit = 0;
        m_nStartScrollPos = 0;
        m_nStartSize = 0;
        m_nStartPos = 0;
        m_ptDownPoint = 0;
        m_ptSelectPoint = 0;
        m_rcGroupSelect = 0;
        m_dwSearchTick = 0;
        m_dwScrollTick = 0;
        m_strSearchString = _T("");
    }

    ~CListImpl()
    {
    }

protected:
    BOOL m_bShowHeader;
    BOOL m_bShowThemed;
    BOOL m_bShowSort;
    BOOL m_bSortAscending;
    BOOL m_bButtonDown;
    BOOL m_bMouseOver;
    BOOL m_bColumnSizing;
    BOOL m_bBeginSelect;
    BOOL m_bSingleSelect;
    BOOL m_bFocusSubItem;
    BOOL m_bGroupSelect;
    BOOL m_bShowHorizScroll;
    BOOL m_bShowVertScroll;
    BOOL m_bEnableHorizScroll;
    BOOL m_bEnableVertScroll;
    BOOL m_bResizeTimer;
    BOOL m_bDragDrop;
    BOOL m_bSmoothScroll;
    BOOL m_bEditItem;
    BOOL m_bScrolling;
    BOOL m_bScrollDown;
    BOOL m_bTileBackground;
    CPoint m_ptDownPoint;
    CPoint m_ptSelectPoint;
    CRect m_rcGroupSelect;
    int m_nItemHeight;
    int m_nHeaderHeight;
    int m_nFirstSelected;
    int m_nFocusItem;
    int m_nFocusSubItem;
    int m_nHotItem;
    int m_nHotSubItem;
    int m_nTitleTipItem;
    int m_nTitleTipSubItem;
    int m_nMouseWheelScroll;
    int m_nTotalWidth;
    int m_nSortColumn;
    int m_nDragColumn;
    int m_nHighlightColumn;
    int m_nHotColumn;
    int m_nHotDivider;
    int m_nColumnSizing;
    int m_nScrollOffset;
    int m_nScrollDelta;
    int m_nScrollUnit;
    int m_nStartScrollPos;
    int m_nStartSize;
    int m_nStartPos;
    DWORD m_dwSearchTick;
    DWORD m_dwScrollTick;
    CString m_strSearchString;
    CBitmap m_bmpScrollList;
    CBitmap m_bmpBackground;

    UINT m_nHeaderClipboardFormat;

    COLORREF m_rgbBackground;
    COLORREF m_rgbHeaderBackground;
    COLORREF m_rgbHeaderBorder;
    COLORREF m_rgbHeaderShadow;
    COLORREF m_rgbHeaderText;
    COLORREF m_rgbHeaderHighlight;
    COLORREF m_rgbSelectedItem;
    COLORREF m_rgbSelectedText;
    COLORREF m_rgbItemText;
    COLORREF m_rgbSelectOuter;
    COLORREF m_rgbSelectInner;
    COLORREF m_rgbSelectTop;
    COLORREF m_rgbSelectBottom;
    COLORREF m_rgbNoFocusTop;
    COLORREF m_rgbNoFocusBottom;
    COLORREF m_rgbNoFocusOuter;
    COLORREF m_rgbNoFocusInner;
    COLORREF m_rgbFocusTop;
    COLORREF m_rgbFocusBottom;
    COLORREF m_rgbProgressTop;
    COLORREF m_rgbProgressBottom;
    COLORREF m_rgbItemFocus;
    COLORREF m_rgbHyperLink;

    CTheme m_thmHeader;
    CTheme m_thmProgress;
    CCursor m_curDivider;
    CCursor m_curHyperLink;
    CFont m_fntListFont;
    CFont m_fntUnderlineFont;
    CImageList m_ilListItems;
    CImageList m_ilItemImages;
    CDragDrop < CListImpl > m_oleDragDrop;
    CToolTipCtrl m_ttToolTip;
    CDropArrows m_wndDropArrows;
    CTitleTip m_wndTitleTip;
    CListEdit m_wndItemEdit;
    CListCombo m_wndItemCombo;
    CListDate m_wndItemDate;

    CListArray < CListColumn > m_aColumns;
    set < int > m_setSelectedItems;

public:
    BOOL SubclassWindow(HWND hWnd)
    {
        T* pT = static_cast<T*>(this);
        return CWindowImpl< CListImpl >::SubclassWindow(hWnd) ? pT->Initialise() : FALSE;
    }

    void RegisterClass()
    {
        T* pT = static_cast<T*>(this);
        pT->GetWndClassInfo().m_wc.lpfnWndProc = m_pfnSuperWindowProc;
        pT->GetWndClassInfo().Register(&m_pfnSuperWindowProc);
    }

    BOOL Initialise()
    {
        // load list images
        if (!m_ilListItems.CreateFromImage(IDB_LISTITEMS, 16, 0, RGB(255, 0, 255), IMAGE_BITMAP, LR_CREATEDIBSECTION))
            return FALSE;

        if (m_curDivider.LoadCursor(IDC_DIVIDER) == NULL)
            return FALSE;

        if (m_curHyperLink.LoadCursor(IDC_HYPERLINK) == NULL)
            return FALSE;

        // load interface settings
        if (!LoadSettings())
            return FALSE;

        // give control a static border
        ModifyStyle(WS_BORDER, WS_CLIPCHILDREN);
        ModifyStyleEx(WS_EX_CLIENTEDGE, WS_EX_STATICEDGE, SWP_FRAMECHANGED);
        // register drag drop
        m_oleDragDrop.Register(this);
        m_oleDragDrop.AddTargetFormat(m_nHeaderClipboardFormat);
        m_oleDragDrop.AddSourceFormat(m_nHeaderClipboardFormat);

        // create the tooltip
        if (!m_ttToolTip.Create(m_hWnd))
            return FALSE;

        m_ttToolTip.SetMaxTipWidth(SHRT_MAX);
        return TRUE;
    }

    BOOL LoadSettings()
    {
        m_rgbBackground = GetSysColor(COLOR_WINDOW);
        m_rgbHeaderBackground = GetSysColor(COLOR_BTNFACE);
        m_rgbHeaderBorder = GetSysColor(COLOR_3DHIGHLIGHT);
        m_rgbHeaderShadow = GetSysColor(COLOR_3DSHADOW);
        m_rgbHeaderText = GetSysColor(COLOR_WINDOWTEXT);
        m_rgbHeaderHighlight = RGB(130, 140, 180);
        m_rgbSelectedItem = GetSysColor(COLOR_HIGHLIGHT);
        m_rgbSelectedText = GetSysColor(COLOR_HIGHLIGHTTEXT);
        m_rgbItemText = GetSysColor(COLOR_WINDOWTEXT);
        m_rgbSelectOuter = RGB(170, 200, 245);
        m_rgbSelectInner = RGB(230, 250, 250);
        m_rgbSelectTop = RGB(210, 240, 250);
        m_rgbSelectBottom = RGB(185, 215, 250);
        m_rgbNoFocusTop = RGB(250, 250, 250);
        m_rgbNoFocusBottom = RGB(235, 235, 235);
        m_rgbNoFocusOuter = RGB(220, 220, 220);
        m_rgbNoFocusInner = RGB(245, 245, 245);
        m_rgbFocusTop = RGB(235, 245, 245);
        m_rgbFocusBottom = RGB(225, 235, 245);
        m_rgbProgressTop = RGB(170, 240, 170);
        m_rgbProgressBottom = RGB(45, 210, 50);
        m_rgbItemFocus = RGB(180, 190, 210);
        m_rgbHyperLink = RGB(0, 0, 200);
        m_nHeaderClipboardFormat = RegisterClipboardFormat(_T("HEADERCLIPBOARDFORMAT"));
        // get number of lines to scroll
        SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &m_nMouseWheelScroll, 0);
        // get system message font
        CLogFont logFont;
        logFont.SetMessageBoxFont();

        if (!m_fntListFont.IsNull())
            m_fntListFont.DeleteObject();

        if (m_fntListFont.CreateFontIndirect(&logFont) == NULL)
            return FALSE;

        // get system underline font
        logFont.lfUnderline = BYTE(TRUE);

        if (!m_fntUnderlineFont.IsNull())
            m_fntUnderlineFont.DeleteObject();

        if (m_fntUnderlineFont.CreateFontIndirect(&logFont) == NULL)
            return FALSE;

        CClientDC dcClient(m_hWnd);
        HFONT hOldFont = dcClient.SelectFont(m_fntListFont);
        CSize sizeExtent;

        if (!dcClient.GetTextExtent(_T("Height"), -1, &sizeExtent))
            return FALSE;

        dcClient.SelectFont(hOldFont);

        // has system font changed
        if (m_nItemHeight != sizeExtent.cy + ITEM_HEIGHT_MARGIN) {
            m_nItemHeight = sizeExtent.cy + ITEM_HEIGHT_MARGIN;
            m_nHeaderHeight = m_nItemHeight;

            // create drop arrows window
            if (m_wndDropArrows.IsWindow())
                m_wndDropArrows.DestroyWindow();

            if (!m_wndDropArrows.Create(m_hWnd, m_nHeaderHeight, TRUE))
                return FALSE;
        }

        // open header theme
        if (!m_thmHeader.IsThemeNull())
            m_thmHeader.CloseThemeData();

        m_thmHeader.OpenThemeData(m_hWnd, L"HEADER");

        // open progress theme
        if (!m_thmProgress.IsThemeNull())
            m_thmProgress.CloseThemeData();

        m_thmProgress.OpenThemeData(m_hWnd, L"PROGRESS");

        // create titletip window
        if (m_wndTitleTip.IsWindow())
            m_wndTitleTip.DestroyWindow();

        if (!m_wndTitleTip.Create(m_hWnd, m_bShowThemed))
            return FALSE;

        return TRUE;
    }

    void ShowThemed(BOOL bShowThemed = TRUE)
    {
        m_bShowThemed = bShowThemed;
        LoadSettings();
        Invalidate();
    }

    void ShowHeader(BOOL bShowHeader = TRUE)
    {
        m_bShowHeader = bShowHeader;
        ResetScrollBars();
        Invalidate();
    }

    void ShowHeaderSort(BOOL bShowSort = TRUE)
    {
        m_bShowSort = bShowSort;
        Invalidate();
    }

    void SetSingleSelect(BOOL bSingleSelect = TRUE)
    {
        m_bSingleSelect = bSingleSelect;
        Invalidate();
    }

    void SetFocusSubItem(BOOL bFocusSubItem = TRUE)
    {
        m_bFocusSubItem = bFocusSubItem;
        Invalidate();
    }

    void SetDragDrop(BOOL bDragDrop = TRUE)
    {
        m_bDragDrop = bDragDrop;
    }

    void SetSmoothScroll(BOOL bSmoothScroll = TRUE)
    {
        m_bSmoothScroll = bSmoothScroll;
    }

    void SetBackgroundImage(HBITMAP hBackgroundImage, BOOL bTileImage = FALSE)
    {
        m_bmpBackground = hBackgroundImage;
        m_bTileBackground = bTileImage;
    }

    void SetImageList(CImageList& ilItemImages)
    {
        m_ilItemImages = ilItemImages;
    }

    UINT ValidateFlags(UINT nFlags)
    {
        if (nFlags & ITEM_FLAGS_CENTRE)
            nFlags &= ~(ITEM_FLAGS_LEFT | ITEM_FLAGS_RIGHT);

        if (nFlags & ITEM_FLAGS_RIGHT)
            nFlags &= ~ITEM_FLAGS_LEFT;

        if (nFlags & ITEM_FLAGS_DATE_ONLY)
            nFlags &= ~ITEM_FLAGS_TIME_ONLY;

        if (nFlags & (ITEM_FLAGS_EDIT_NUMBER | ITEM_FLAGS_EDIT_FLOAT))
            nFlags &= ~ITEM_FLAGS_EDIT_UPPER;

        if (!(nFlags & (ITEM_FLAGS_EDIT_NUMBER | ITEM_FLAGS_EDIT_FLOAT)))
            nFlags &= ~(ITEM_FLAGS_EDIT_NEGATIVE | ITEM_FLAGS_EDIT_OPERATOR);

        if (nFlags & ITEM_FLAGS_COMBO_EDIT)
            nFlags &= ~(ITEM_FLAGS_DATE_ONLY | ITEM_FLAGS_TIME_ONLY | ITEM_FLAGS_DATETIME_NONE);

        return nFlags;
    }

    void AddColumn(CListColumn& listColumn)
    {
        // minimum column width
        if (listColumn.m_strText.IsEmpty() && listColumn.m_nImage != ITEM_IMAGE_NONE) {
            CSize sizeIcon;
            m_ilListItems.GetIconSize(sizeIcon);
            listColumn.m_nWidth = sizeIcon.cx + 5;
            listColumn.m_nFlags |= ITEM_FLAGS_CENTRE;
        }

        // correct incompatible flag mask values
        listColumn.m_nFlags = ValidateFlags(listColumn.m_nFlags);
        // initial data index
        listColumn.m_nIndex = GetColumnCount();
        m_aColumns.Add(listColumn);
        ResetScrollBars();
        Invalidate();
    }

    void AddColumn(LPCTSTR lpszText, int nWidth = 0, int nImage = ITEM_IMAGE_NONE, BOOL bFixed = FALSE, UINT nFormat = ITEM_FORMAT_NONE, UINT nFlags = ITEM_FLAGS_NONE)
    {
        CListColumn listColumn;
        listColumn.m_strText = lpszText;
        listColumn.m_nWidth = nWidth;
        listColumn.m_bFixed = bFixed;
        listColumn.m_nFormat = nFormat;
        listColumn.m_nFlags = nFlags;
        listColumn.m_nImage = nImage;
        AddColumn(listColumn);
    }

    int GetColumnCount()
    {
        return m_aColumns.GetSize();
    }

    BOOL GetColumn(int nColumn, CListColumn& listColumn)
    {
        if (nColumn < 0 || nColumn >= GetColumnCount())
            return FALSE;

        listColumn = m_aColumns[ nColumn ];
        return TRUE;
    }

    int GetTotalWidth(BOOL bRecalc = FALSE)
    {
        if (bRecalc) {
            m_nTotalWidth = 0;

            for (int nColumn = 0; nColumn < GetColumnCount(); nColumn++)
                m_nTotalWidth += GetColumnWidth(nColumn);
        }

        return m_nTotalWidth - 1;
    }

    int GetTotalHeight()
    {
        T* pT = static_cast<T*>(this);
        return max((pT->GetItemCount() * m_nItemHeight) + (m_bShowHeader ? m_nHeaderHeight : 0), 1);
    }

    BOOL SetColumnWidth(int nColumn, int nWidth)
    {
        if (nColumn < 0 || nColumn >= GetColumnCount())
            return FALSE;

        // set new column size if not fixed
        if (!m_aColumns[ nColumn ].m_bFixed) {
            m_aColumns[ nColumn ].m_nWidth = nWidth;
            ResetScrollBars();
            Invalidate();
        }

        return TRUE;
    }

    int GetColumnWidth(int nColumn)
    {
        CListColumn listColumn;
        return GetColumn(nColumn, listColumn) ? listColumn.m_nWidth : 0;
    }

    int GetColumnIndex(int nColumn)
    {
        CListColumn listColumn;
        return GetColumn(nColumn, listColumn) ? listColumn.m_nIndex : 0;
    }

    int IndexToOrder(int nIndex)
    {
        for (int nColumn = 0; nColumn < GetColumnCount(); nColumn++) {
            if (GetColumnIndex(nColumn) == nIndex)
                return nColumn;
        }

        return -1;
    }

    BOOL SetColumnFormat(int nColumn, UINT nFormat, UINT nFlags = ITEM_FLAGS_NONE)
    {
        if (nColumn < 0 || nColumn >= GetColumnCount())
            return FALSE;

        m_aColumns[ nColumn ].m_nFormat = nFormat;
        m_aColumns[ nColumn ].m_nFlags = ValidateFlags(nFlags);
        return TRUE;
    }

    BOOL SetColumnFormat(int nColumn, UINT nFormat, UINT nFlags, CListArray < CString >& aComboList)
    {
        if (nColumn < 0 || nColumn >= GetColumnCount())
            return FALSE;

        m_aColumns[ nColumn ].m_nFormat = nFormat;
        m_aColumns[ nColumn ].m_nFlags = ValidateFlags(nFlags);
        m_aColumns[ nColumn ].m_aComboList = aComboList;
        return TRUE;
    }

    UINT GetColumnFormat(int nColumn)
    {
        CListColumn listColumn;
        return GetColumn(nColumn, listColumn) ? listColumn.m_nFormat : ITEM_FORMAT_NONE;
    }

    UINT GetColumnFlags(int nColumn)
    {
        CListColumn listColumn;
        return GetColumn(nColumn, listColumn) ? listColumn.m_nFlags : ITEM_FLAGS_NONE;
    }

    BOOL GetColumnComboList(int nColumn, CListArray < CString >& aComboList)
    {
        CListColumn listColumn;

        if (!GetColumn(nColumn, listColumn))
            return FALSE;

        aComboList = listColumn.m_aComboList;
        return !aComboList.IsEmpty();
    }

    BOOL GetColumnRect(int nColumn, CRect& rcColumn)
    {
        if (nColumn < 0 || nColumn >= GetColumnCount())
            return FALSE;

        GetClientRect(rcColumn);
        rcColumn.bottom = m_nHeaderHeight;

        for (int nColumnOrder = 0; nColumnOrder < GetColumnCount(); nColumnOrder++) {
            int nWidth = GetColumnWidth(nColumnOrder);

            if (nColumn == nColumnOrder) {
                rcColumn.right = rcColumn.left + nWidth;
                break;
            }

            rcColumn.left += nWidth;
        }

        // offset column by scroll position
        rcColumn.OffsetRect(-GetScrollPos(SB_HORZ), 0);
        return TRUE;
    }

    BOOL AddItem()
    {
        ResetScrollBars();
        return Invalidate();
    }

    BOOL DeleteItem(int nItem)
    {
        m_setSelectedItems.erase(nItem);
        ResetScrollBars();
        return Invalidate();
    }

    BOOL DeleteAllItems()
    {
        m_setSelectedItems.clear();
        ResetScrollBars();
        return Invalidate();
    }

    int GetItemCount()
    {
        ATLASSERT(FALSE);   // must be implemented in a derived class
        return 0;
    }

    CString GetItemText(int nItem, int nSubItem)
    {
        ATLASSERT(FALSE);   // must be implemented in a derived class
        return _T("");
    }

    BOOL GetItemDate(int nItem, int nSubItem, SYSTEMTIME& stItemDate)
    {
        T* pT = static_cast<T*>(this);
        ZeroMemory(&stItemDate, sizeof(SYSTEMTIME));
        CString strItemText = pT->GetItemText(nItem, nSubItem);

        if (strItemText.IsEmpty())
            return FALSE;

        // get date-time from item text: yyyymmddhhmmss
        stItemDate.wYear = _ttoi(strItemText.Left(4));
        stItemDate.wMonth = _ttoi(strItemText.Mid(4, 2));
        stItemDate.wDay = _ttoi(strItemText.Mid(6, 2));
        stItemDate.wHour = _ttoi(strItemText.Mid(8, 2));
        stItemDate.wMinute = _ttoi(strItemText.Mid(10, 2));
        stItemDate.wSecond = _ttoi(strItemText.Mid(12, 2));
        stItemDate.wMilliseconds = 0;
        return TRUE;
    }

    int GetItemImage(int nItem, int nSubItem)
    {
        return ITEM_IMAGE_NONE; // may be implemented in a derived class
    }

    UINT GetItemFormat(int nItem, int nSubItem)
    {
        return GetColumnFormat(IndexToOrder(nSubItem));     // may be implemented in a derived class
    }

    UINT GetItemFlags(int nItem, int nSubItem)
    {
        return GetColumnFlags(IndexToOrder(nSubItem));     // may be implemented in a derived class
    }

    BOOL GetItemComboList(int nItem, int nSubItem, CListArray < CString >& aComboList)
    {
        return GetColumnComboList(IndexToOrder(nSubItem), aComboList);     // may be implemented in a derived class
    }

    HFONT GetItemFont(int nItem, int nSubItem)
    {
        return m_fntListFont; // may be implemented in a derived class
    }

    BOOL GetItemColours(int nItem, int nSubItem, COLORREF& rgbBackground, COLORREF& rgbText)
    {
        rgbBackground = m_rgbBackground;
        rgbText = m_rgbItemText;
        return TRUE;
    }

    CString GetItemToolTip(int nItem, int nSubItem)
    {
        return _T("");   // may be implemented in a derived class
    }

    BOOL SetItemText(int nItem, int nSubItem, LPCTSTR lpszText)
    {
        ATLASSERT(FALSE);   // must be implemented in a derived class
        return FALSE;
    }

    BOOL SetItemComboIndex(int nItem, int nSubItem, int nIndex)
    {
        ATLASSERT(FALSE);   // must be implemented in a derived class
        return FALSE;
    }

    BOOL SetItemDate(int nItem, int nSubItem, SYSTEMTIME& stItemDate)
    {
        T* pT = static_cast<T*>(this);
        // set date-time in format (yyyymmddhhmmss)
        CString strFormatDate;
        strFormatDate.Format(_T("%04d%02d%02d%02d%02d%02d"), stItemDate.wYear, stItemDate.wMonth, stItemDate.wDay, stItemDate.wHour, stItemDate.wMinute, stItemDate.wSecond);
        return pT->SetItemText(nItem, nSubItem, strFormatDate);
    }

    BOOL SetItemCheck(int nItem, int nSubItem, int nCheckValue)
    {
        T* pT = static_cast<T*>(this);

        switch (pT->GetItemFormat(nItem, nSubItem)) {
        case ITEM_FORMAT_CHECKBOX:
            return pT->SetItemText(nItem, nSubItem, nCheckValue > 0 ? _T("1") : _T("0"));

        case ITEM_FORMAT_CHECKBOX_3STATE:
            if (nCheckValue < 0)
                return pT->SetItemText(nItem, nSubItem, _T("-1"));

            if (nCheckValue > 0)
                return pT->SetItemText(nItem, nSubItem, _T("1"));

            return pT->SetItemText(nItem, nSubItem, _T("0"));
        }

        return FALSE;
    }

    BOOL SetItemImage(int nItem, int nSubItem, int nImage)
    {
        ATLASSERT(FALSE);   // must be implemented in a derived class
        return FALSE;
    }

    BOOL SetItemFormat(int nItem, int nSubItem, UINT nFormat, UINT nFlags = ITEM_FLAGS_NONE)
    {
        ATLASSERT(FALSE);   // must be implemented in a derived class
        return FALSE;
    }

    BOOL SetItemFormat(int nItem, int nSubItem, UINT nFormat, UINT nFlags, CListArray < CString >& aComboList)
    {
        ATLASSERT(FALSE);   // must be implemented in a derived class
        return FALSE;
    }

    BOOL SetItemFont(int nItem, int nSubItem, HFONT hFont)
    {
        ATLASSERT(FALSE);   // must be implemented in a derived class
        return FALSE;
    }

    BOOL SetItemColours(int nItem, int nSubItem, COLORREF rgbBackground, COLORREF rgbText)
    {
        ATLASSERT(FALSE);   // must be implemented in a derived class
        return FALSE;
    }

    void ReverseItems()
    {
        ATLASSERT(FALSE);   // must be implemented in a derived class
    }

    void SortItems(int nColumn, BOOL bAscending)
    {
        ATLASSERT(FALSE);   // must be implemented in a derived class
    }

    BOOL GetItemRect(int nItem, int nSubItem, CRect& rcItem)
    {
        T* pT = static_cast<T*>(this);
        int nTopItem = GetTopItem();

        if (nItem < nTopItem || nItem >= pT->GetItemCount() || nItem >= nTopItem + GetCountPerPage())
            return FALSE;

        CRect rcClient;
        GetClientRect(rcClient);
        // calculate item rect based on scroll position
        rcItem = rcClient;
        rcItem.top = (m_bShowHeader ? m_nHeaderHeight : 0) + ((nItem - nTopItem) * m_nItemHeight);
        rcItem.bottom = rcItem.top + m_nItemHeight;
        rcItem.right = min(rcClient.right, GetTotalWidth());

        if (nSubItem != NULL_SUBITEM) {
            CRect rcColumn;

            if (!GetColumnRect(nSubItem, rcColumn))
                return FALSE;

            rcItem.left = rcColumn.left;
            rcItem.right = rcColumn.right;
        }

        return TRUE;
    }

    BOOL GetItemRect(int nItem, CRect& rcItem)
    {
        return GetItemRect(nItem, NULL_SUBITEM, rcItem);
    }

    BOOL InvalidateItem(int nItem, int nSubItem = NULL_SUBITEM)
    {
        CRect rcItem;
        return GetItemRect(nItem, nSubItem, rcItem) ? InvalidateRect(rcItem) : FALSE;
    }

    BOOL InvalidateHeader()
    {
        if (!m_bShowHeader)
            return TRUE;

        CRect rcHeader;

        if (!GetClientRect(rcHeader))
            return FALSE;

        rcHeader.bottom = m_nHeaderHeight;
        return InvalidateRect(rcHeader);
    }

    int GetTopItem()
    {
        return (int)(GetScrollPos(SB_VERT) / m_nItemHeight);
    }

    int GetCountPerPage(BOOL bPartial = TRUE)
    {
        CRect rcClient;
        GetClientRect(rcClient);
        rcClient.top = (m_bShowHeader ? m_nHeaderHeight : 0);
        // calculate number of items per control height (include partial item)
        div_t divHeight = div(rcClient.Height(), m_nItemHeight);
        // round up to nearest item count
        return max(bPartial && divHeight.rem > 0 ? divHeight.quot + 1 : divHeight.quot, 1);
    }

    BOOL IsItemVisible(int nItem, int nSubItem = NULL_SUBITEM, BOOL bPartial = TRUE)
    {
        T* pT = static_cast<T*>(this);
        int nTopItem = GetTopItem();

        if (nItem < nTopItem || nItem >= pT->GetItemCount())
            return FALSE;

        // check whether item is visible
        if (nItem < nTopItem || nItem >= nTopItem + GetCountPerPage(bPartial))
            return FALSE;

        // check whether subitem is visible
        if (m_bFocusSubItem && nSubItem != NULL_SUBITEM) {
            CRect rcColumn;

            if (!GetColumnRect(nSubItem, rcColumn))
                return FALSE;

            CRect rcClient;
            GetClientRect(rcClient);

            if (rcColumn.left < rcClient.left || rcColumn.right > rcClient.right)
                return FALSE;
        }

        return TRUE;
    }

    BOOL EnsureItemVisible(int nItem, int nSubItem = NULL_SUBITEM)
    {
        if (IsItemVisible(nItem, nSubItem, FALSE))
            return TRUE;

        HideTitleTip();
        CRect rcClient;
        GetClientRect(rcClient);
        rcClient.top = (m_bShowHeader ? m_nHeaderHeight : 0);
        CRect rcItem;
        rcItem.top = (m_bShowHeader ? m_nHeaderHeight : 0) + ((nItem - GetTopItem()) * m_nItemHeight);
        rcItem.bottom = rcItem.top + m_nItemHeight;

        if (rcItem.top < rcClient.top || rcItem.bottom > rcClient.bottom) {
            int nScrollItem = NULL_ITEM;

            // scroll list up/down to include item
            if (rcItem.top < rcClient.top || rcItem.Height() > rcClient.Height())
                nScrollItem = nItem;
            else if (rcItem.bottom > rcClient.bottom)
                nScrollItem = nItem - (GetCountPerPage(FALSE) - 1);

            if (nScrollItem != NULL_ITEM)
                SetScrollPos(SB_VERT, nScrollItem * m_nItemHeight);
        }

        if (m_bFocusSubItem && nSubItem != NULL_SUBITEM) {
            CRect rcColumn;

            if (!GetColumnRect(nSubItem, rcColumn))
                return FALSE;

            CRect rcClient;
            GetClientRect(rcClient);
            int nScrollPos = 0;

            // scroll list left/right to include subitem
            if (rcColumn.Width() > rcClient.Width() || rcColumn.left < 0)
                nScrollPos = rcColumn.left;
            else if (rcColumn.right > rcClient.right)
                nScrollPos = rcColumn.right - rcClient.right;

            if (nScrollPos != 0)
                SetScrollPos(SB_HORZ, GetScrollPos(SB_HORZ) + nScrollPos);
        }

        return Invalidate();
    }

    void ShowScrollBar(int nScrollBar, BOOL bShow = TRUE)
    {
        switch (nScrollBar) {
        case SB_HORZ:
            m_bShowHorizScroll = bShow;
            break;

        case SB_VERT:
            m_bShowVertScroll = bShow;
            break;

        case SB_BOTH:
            m_bShowHorizScroll = bShow;
            m_bShowVertScroll = bShow;
            break;
        }

        ResetScrollBars();
        Invalidate();
    }

    void ResetScrollBars(int nScrollBar = SB_BOTH, int nScrollPos = -1, BOOL bRecalc = TRUE)
    {
        T* pT = static_cast<T*>(this);
        CRect rcClient;
        GetClientRect(rcClient);
        SCROLLINFO infoScroll;
        infoScroll.cbSize = sizeof(SCROLLINFO);
        infoScroll.fMask = nScrollPos < 0 ? SIF_PAGE | SIF_RANGE : SIF_PAGE | SIF_RANGE | SIF_POS;
        infoScroll.nPos = nScrollPos;
        infoScroll.nMin = 0;

        if ((nScrollBar == SB_BOTH || nScrollBar == SB_VERT) && m_bShowVertScroll) {
            infoScroll.nMax = (pT->GetItemCount() * m_nItemHeight) + (m_bShowHeader ? m_nHeaderHeight : 0);
            infoScroll.nPage = rcClient.Height() - (m_bShowHeader ? m_nHeaderHeight : 0);

            // are we within client range?
            if ((UINT)infoScroll.nMax <= infoScroll.nPage + (m_bShowHeader ? m_nHeaderHeight : 0))
                infoScroll.nMax = 0;

            // set vertical scroll bar
            m_bEnableVertScroll = SetScrollInfo(SB_VERT, &infoScroll, TRUE) ? (infoScroll.nMax > 0) : FALSE;
        }

        if ((nScrollBar == SB_BOTH || nScrollBar == SB_HORZ) && m_bShowHorizScroll) {
            infoScroll.nMax = GetTotalWidth(bRecalc);
            infoScroll.nPage = rcClient.Width();

            // are we within client range?
            if (infoScroll.nPage >= (UINT)infoScroll.nMax)
                infoScroll.nMax = 0;

            // set horizontal scroll bar
            m_bEnableHorizScroll = SetScrollInfo(SB_HORZ, &infoScroll, TRUE) ? (infoScroll.nMax > (int)infoScroll.nPage) : FALSE;
        }
    }

    BOOL IsScrollBarVisible(int nScrollBar)
    {
        switch (nScrollBar) {
        case SB_HORZ:
            return m_bEnableHorizScroll;

        case SB_VERT:
            return m_bEnableVertScroll;

        case SB_BOTH:
            return (m_bEnableHorizScroll && m_bEnableVertScroll);

        default:
            return FALSE;
        }
    }

    BOOL ResetSelected()
    {
        m_setSelectedItems.clear();
        m_nFocusItem = NULL_ITEM;
        m_nFocusSubItem = NULL_SUBITEM;
        m_nFirstSelected = NULL_ITEM;
        return Invalidate();
    }

    BOOL SelectItem(int nItem, int nSubItem = NULL_SUBITEM, UINT nFlags = 0)
    {
        T* pT = static_cast<T*>(this);

        if (nItem < 0 || nItem >= pT->GetItemCount())
            return FALSE;

        BOOL bSelectItem = TRUE;
        BOOL bSelectRange = !m_bSingleSelect && (nFlags & MK_SHIFT);
        BOOL bNewSelect = !(bSelectRange || (nFlags & MK_CONTROL));
        BOOL bEnsureVisible = FALSE;

        // are we starting a new select sequence?
        if (bNewSelect || bSelectRange) {
            // are we simply reselecting the same item?
            if (m_setSelectedItems.size() == 1 && *m_setSelectedItems.begin() == nItem) {
                bSelectItem = FALSE;
                m_nFirstSelected = nItem;
                m_nFocusItem = nItem;
                m_nFocusSubItem = nSubItem;
            } else
                m_setSelectedItems.clear();
        } else { // we adding to or removing from select sequence
            if (m_bSingleSelect)
                m_setSelectedItems.clear();

            set < int >::iterator posSelectedItem = m_setSelectedItems.find(nItem);

            // is this item already selected?
            if (posSelectedItem != m_setSelectedItems.end()) {
                bSelectItem = FALSE;
                m_setSelectedItems.erase(posSelectedItem);
                m_nFirstSelected = nItem;
                m_nFocusItem = nItem;
                m_nFocusSubItem = m_setSelectedItems.size() > 1 ? NULL_SUBITEM : nSubItem;
            }
        }

        // are we adding this item to the select sequence?
        if (bSelectItem) {
            bEnsureVisible = TRUE;

            if (bSelectRange) {
                if (m_nFirstSelected == NULL_ITEM)
                    m_nFirstSelected = nItem;

                for (int nSelectedItem = min(m_nFirstSelected, nItem); nSelectedItem <= max(m_nFirstSelected, nItem); nSelectedItem++)
                    m_setSelectedItems.insert(nSelectedItem);
            } else {
                m_nFirstSelected = nItem;
                m_setSelectedItems.insert(nItem);
            }

            m_nFocusItem = nItem;
            m_nFocusSubItem = m_setSelectedItems.size() > 1 ? NULL_SUBITEM : nSubItem;
            // notify parent of selected item
            NotifyParent(m_nFocusItem, m_nFocusSubItem, LCN_SELECTED);
        }

        // start visible timer (scrolls list to partially hidden item)
        if (!IsItemVisible(nItem, m_setSelectedItems.size() > 1 ? NULL_SUBITEM : nSubItem, FALSE))
            SetTimer(ITEM_VISIBLE_TIMER, ITEM_VISIBLE_PERIOD);
        else if (m_nFocusItem != NULL_ITEM && m_nFocusSubItem != NULL_SUBITEM)
            EditItem(m_nFocusItem, m_nFocusSubItem);

        return Invalidate();
    }

    BOOL IsSelected(int nItem)
    {
        set < int >::iterator posSelectedItem = m_setSelectedItems.find(nItem);
        return (posSelectedItem != m_setSelectedItems.end());
    }

    BOOL GetSelectedItems(CListArray < int >& aSelectedItems)
    {
        aSelectedItems.RemoveAll();

        for (set < int >::iterator posSelectedItem = m_setSelectedItems.begin(); posSelectedItem != m_setSelectedItems.end(); ++posSelectedItem)
            aSelectedItems.Add(*posSelectedItem);

        return !aSelectedItems.IsEmpty();
    }

    BOOL SetFocusItem(int nItem, int nSubItem = NULL_SUBITEM)
    {
        m_nFocusItem = nItem;
        m_nFocusSubItem = nSubItem;
        return EnsureItemVisible(m_nFocusItem, m_nFocusSubItem);
    }

    BOOL GetFocusItem(int& nItem, int& nSubItem)
    {
        nItem = IsSelected(m_nFocusItem) ? m_nFocusItem : (m_setSelectedItems.empty() ? NULL_ITEM : *m_setSelectedItems.begin());
        nSubItem = !m_bFocusSubItem || nItem == NULL_ITEM ? NULL_SUBITEM : m_nFocusSubItem;
        return (nItem != NULL_ITEM);
    }

    int GetFocusItem()
    {
        return IsSelected(m_nFocusItem) ? m_nFocusItem : (m_setSelectedItems.empty() ? NULL_ITEM : *m_setSelectedItems.begin());
    }

    BOOL HitTestHeader(CPoint point, int& nColumn, UINT& nFlags)
    {
        // reset hittest flags
        nFlags = HITTEST_FLAG_NONE;

        if (!m_bShowHeader)
            return FALSE;

        CRect rcClient;

        if (!GetClientRect(rcClient))
            return FALSE;

        // are we over the header?
        if (point.y < rcClient.top || point.y > m_nHeaderHeight)
            return FALSE;

        int nDividerPos = 0;
        int nColumnCount = GetColumnCount();

        // get hit-test subitem
        for (nColumn = 0; nColumn < nColumnCount; nColumn++) {
            int nColumnWidth = GetColumnWidth(nColumn);
            nDividerPos += nColumnWidth;
            // offset divider position with current scroll position
            int nRelativePos = nDividerPos - GetScrollPos(SB_HORZ);

            // are we over the divider zone?
            if (point.x >= nRelativePos - DRAG_HEADER_OFFSET - 1 && point.x <= nRelativePos + DRAG_HEADER_OFFSET) {
                nFlags |= HITTEST_FLAG_HEADER_DIVIDER;

                // are we to the left of the divider (or over last column divider)?
                if ((point.x >= nRelativePos - DRAG_HEADER_OFFSET - 1 && point.x < nRelativePos) || nColumn + 1 >= nColumnCount - 1) {
                    nFlags |= HITTEST_FLAG_HEADER_LEFT;
                    return TRUE;
                }

                // find last zero-length column after this column
                for (int nNextColumn = nColumn + 1; nNextColumn < nColumnCount; nNextColumn++) {
                    if (GetColumnWidth(nNextColumn) > 0)
                        break;

                    nColumn = nNextColumn;
                }

                nFlags |= HITTEST_FLAG_HEADER_RIGHT;
                return TRUE;
            }

            // are we over a column?
            if (point.x > nRelativePos - nColumnWidth && point.x < nRelativePos)
                return TRUE;
        }

        return FALSE;
    }

    BOOL HitTest(CPoint point, int& nItem, int& nSubItem)
    {
        T* pT = static_cast<T*>(this);

        // are we over the header?
        if (point.y < (m_bShowHeader ? m_nHeaderHeight : 0))
            return FALSE;

        // calculate hit test item
        nItem = GetTopItem() + (int)((point.y - (m_bShowHeader ? m_nHeaderHeight : 0)) / m_nItemHeight);

        if (nItem < 0 || nItem >= pT->GetItemCount())
            return FALSE;

        int nTotalWidth = 0;
        int nColumnCount = GetColumnCount();

        // get hit-test subitem
        for (nSubItem = 0; nSubItem < nColumnCount; nSubItem++) {
            int nColumnWidth = GetColumnWidth(nSubItem);
            nTotalWidth += nColumnWidth;
            // offset position with current scroll position
            int nRelativePos = nTotalWidth - GetScrollPos(SB_HORZ);

            // are we over a subitem?
            if (point.x > nRelativePos - nColumnWidth && point.x < nRelativePos)
                return TRUE;
        }

        return FALSE;
    }

    BOOL AutoSizeColumn(int nColumn)
    {
        T* pT = static_cast<T*>(this);
        CListColumn listColumn;

        if (!GetColumn(nColumn, listColumn) || listColumn.m_bFixed)
            return FALSE;

        CClientDC dcClient(m_hWnd);
        HFONT hOldFont = dcClient.SelectFont(m_fntListFont);
        // set to column text width if zero-length
        CSize sizeExtent;

        if (!dcClient.GetTextExtent(listColumn.m_strText, -1, &sizeExtent))
            return FALSE;

        int nMaxWidth = sizeExtent.cx + ITEM_WIDTH_MARGIN;
        CSize sizeIcon = 0;

        if (!m_ilItemImages.IsNull())
            m_ilItemImages.GetIconSize(sizeIcon);

        // calculate maximum column width required
        for (int nItem = 0; nItem < pT->GetItemCount(); nItem++) {
            if (!dcClient.GetTextExtent(pT->GetItemText(nItem, listColumn.m_nIndex), -1, &sizeExtent))
                return FALSE;

            if (!m_ilItemImages.IsNull() && pT->GetItemImage(nItem, listColumn.m_nIndex) != ITEM_IMAGE_NONE)
                sizeExtent.cx += sizeIcon.cx;

            nMaxWidth = max(nMaxWidth, (int)sizeExtent.cx + ITEM_WIDTH_MARGIN);
        }

        dcClient.SelectFont(hOldFont);
        return SetColumnWidth(nColumn, nMaxWidth);
    }

    void ResizeColumn(BOOL bColumnScroll = FALSE)
    {
        HideTitleTip();
        int nCurrentPos = GET_X_LPARAM(GetMessagePos());
        CRect rcClient;
        GetClientRect(rcClient);
        int nScrollLimit = GetTotalWidth() - rcClient.Width();

        if (bColumnScroll) {
            // have we finished scrolling list to accommodate new column size?
            if (!m_bColumnSizing || !m_bEnableHorizScroll || nCurrentPos - m_nStartScrollPos > 0) {
                KillTimer(RESIZE_COLUMN_TIMER);
                // reset resize start point
                m_nStartPos = nCurrentPos;
                m_bResizeTimer = FALSE;
            } else if (nCurrentPos < m_nStartPos && GetScrollPos(SB_HORZ) >= nScrollLimit) {
                // reset start column size
                m_nStartSize = max(GetColumnWidth(m_nColumnSizing) + (nCurrentPos - m_nStartScrollPos), 0);
                // resize column
                SetColumnWidth(m_nColumnSizing, m_nStartSize);
            }
        } else {
            int nColumnSize = max(m_nStartSize + (nCurrentPos - m_nStartPos), 0);

            // are we scrolled fully to the right and wanting to reduce the size of a column?
            if (m_bEnableHorizScroll && GetScrollPos(SB_HORZ) >= nScrollLimit && nColumnSize < GetColumnWidth(m_nColumnSizing)) {
                if (!m_bResizeTimer) {
                    // only start the scroll timer once
                    m_bResizeTimer = TRUE;
                    // set new start scroll position
                    m_nStartScrollPos = nCurrentPos;
                    // start column resize / scroll timer
                    SetTimer(RESIZE_COLUMN_TIMER, RESIZE_COLUMN_PERIOD);
                }
            } else {
                // resizing is done in scroll timer (if started)
                if (!m_bResizeTimer)
                    SetColumnWidth(m_nColumnSizing, nColumnSize);
            }
        }
    }

    void DragColumn()
    {
        HideTitleTip();
        CRect rcColumn;

        if (!GetColumnRect(m_nHighlightColumn, rcColumn))
            return;

        CRect rcHeaderItem(rcColumn);
        rcHeaderItem.MoveToXY(0, 0);
        CListColumn listColumn;

        if (!GetColumn(m_nHighlightColumn, listColumn))
            return;

        // store drag column
        m_nDragColumn = m_nHighlightColumn;
        CClientDC dcClient(m_hWnd);
        CDC dcHeader;
        dcHeader.CreateCompatibleDC(dcClient);
        int nContextState = dcHeader.SaveDC();
        // create drag header bitmap
        CBitmapHandle bmpHeader;
        bmpHeader.CreateCompatibleBitmap(dcClient, rcHeaderItem.Width(), rcHeaderItem.Height());
        dcHeader.SelectBitmap(bmpHeader);

        if (m_bShowThemed && !m_thmHeader.IsThemeNull())
            m_thmHeader.DrawThemeBackground(dcHeader, HP_HEADERITEM, HIS_PRESSED, rcHeaderItem, NULL);
        else {
            dcHeader.SetBkColor(m_rgbHeaderBackground);
            dcHeader.ExtTextOut(rcHeaderItem.left, rcHeaderItem.top, ETO_OPAQUE, rcHeaderItem, _T(""), 0, NULL);
            dcHeader.Draw3dRect(rcHeaderItem, m_rgbHeaderBorder, m_rgbHeaderShadow);
        }

        CRect rcHeaderText(rcHeaderItem);
        rcHeaderText.left += m_nHighlightColumn == 0 ? 4 : 3;
        rcHeaderText.OffsetRect(0, 1);
        // margin header text
        rcHeaderText.DeflateRect(4, 0, 5, 0);

        // has this header item an associated image?
        if (listColumn.m_nImage != ITEM_IMAGE_NONE) {
            CSize sizeIcon;
            m_ilListItems.GetIconSize(sizeIcon);
            CRect rcHeaderImage;
            rcHeaderImage.left = listColumn.m_strText.IsEmpty() ? ((rcHeaderText.left + rcHeaderText.right) / 2) - (sizeIcon.cx / 2) - ((!m_bShowThemed || m_thmHeader.IsThemeNull()) ? 0 : 1) : rcHeaderText.left;
            rcHeaderImage.right = min(rcHeaderImage.left + sizeIcon.cx, rcHeaderItem.right);
            rcHeaderImage.top = ((rcHeaderItem.top + rcHeaderItem.bottom) / 2) - (sizeIcon.cy / 2);
            rcHeaderImage.bottom = min(rcHeaderImage.top + sizeIcon.cy, rcHeaderItem.bottom);
            m_ilListItems.DrawEx(listColumn.m_nImage, dcHeader, rcHeaderImage, CLR_DEFAULT, CLR_DEFAULT, ILD_TRANSPARENT);
            // offset header text (for image)
            rcHeaderText.left += sizeIcon.cx + 4;
        }

        dcHeader.SelectFont(m_fntListFont);
        dcHeader.SetTextColor(m_rgbHeaderText);
        dcHeader.SetBkMode(TRANSPARENT);
        UINT nFormat = DT_SINGLELINE | DT_NOPREFIX | DT_VCENTER | DT_END_ELLIPSIS;

        if (listColumn.m_nFlags & ITEM_FLAGS_CENTRE)
            nFormat |= DT_CENTER;
        else if (listColumn.m_nFlags & ITEM_FLAGS_RIGHT)
            nFormat |= DT_RIGHT;
        else
            nFormat |= DT_LEFT;

        // draw header text
        if (!listColumn.m_strText.IsEmpty())
            dcHeader.DrawText(listColumn.m_strText, listColumn.m_strText.GetLength(), rcHeaderText, nFormat);

        dcHeader.RestoreDC(nContextState);
        SHDRAGIMAGE shDragImage;
        ZeroMemory(&shDragImage, sizeof(SHDRAGIMAGE));
        shDragImage.sizeDragImage.cx = rcHeaderItem.Width();
        shDragImage.sizeDragImage.cy = rcHeaderItem.Height();
        shDragImage.ptOffset.x = rcColumn.Width() / 2;
        shDragImage.ptOffset.y = rcColumn.Height() / 2;
        shDragImage.hbmpDragImage = bmpHeader;
        shDragImage.crColorKey = m_rgbBackground;
        // start header drag operation
        m_oleDragDrop.DoDragDrop(&shDragImage, DROPEFFECT_MOVE);
        // hide drop arrows after moving column
        m_wndDropArrows.Hide();

        if (m_bButtonDown) {
            ReleaseCapture();
            m_bButtonDown = FALSE;
            m_bBeginSelect = FALSE;
            m_ptDownPoint = 0;
            m_ptSelectPoint = 0;
        }

        // finish moving a column
        if (m_nHighlightColumn != NULL_COLUMN) {
            m_nHighlightColumn = NULL_COLUMN;
            InvalidateHeader();
        }

        m_nDragColumn = NULL_COLUMN;
    }

    BOOL DropColumn(CPoint point)
    {
        if (!m_bShowHeader)
            return FALSE;

        m_nHotDivider = NULL_COLUMN;
        m_nHotColumn = NULL_COLUMN;
        UINT nHeaderFlags = HITTEST_FLAG_NONE;

        // are we over the header?
        if (HitTestHeader(point, m_nHotColumn, nHeaderFlags)) {
            CRect rcColumn;

            if (!GetColumnRect(m_nHotColumn, rcColumn))
                return FALSE;

            m_nHotDivider = point.x < ((rcColumn.left + rcColumn.right) / 2) ? m_nHotColumn : m_nHotColumn + 1;

            if (m_nHotDivider == m_nDragColumn || m_nHotDivider == m_nDragColumn + 1)
                m_nHotDivider = NULL_COLUMN;
        }

        if (m_nHotDivider != NULL_COLUMN) {
            CRect rcHeader;
            GetClientRect(rcHeader);
            rcHeader.bottom = m_nHeaderHeight;
            CPoint ptDivider(0, rcHeader.Height() / 2);
            CRect rcColumn;
            int nColumnCount = GetColumnCount();

            // set closest divider position
            if (GetColumnRect(m_nHotDivider < nColumnCount ? m_nHotDivider : nColumnCount - 1, rcColumn))
                ptDivider.x = m_nHotDivider < nColumnCount ? rcColumn.left : rcColumn.right;

            ClientToScreen(&ptDivider);
            // track drop window
            m_wndDropArrows.Show(ptDivider);
            return TRUE;
        }

        m_wndDropArrows.Hide();
        return FALSE;
    }

    BOOL SortColumn(int nColumn)
    {
        T* pT = static_cast<T*>(this);

        if (!m_bShowHeader || !m_bShowSort)
            return FALSE;

        BOOL bReverseSort = FALSE;
        int nSortIndex = GetColumnIndex(nColumn);
        CWaitCursor curWait;

        if (nSortIndex != m_nSortColumn) {
            // sort by new column
            m_bSortAscending = TRUE;
            m_nSortColumn = nSortIndex;
            pT->SortItems(m_nSortColumn, m_bSortAscending);
        } else {
            // toggle sort order if sorting same column
            m_bSortAscending = !m_bSortAscending;
            pT->ReverseItems();
        }

        return ResetSelected();
    }

    BOOL GetSortColumn(int& nColumn, BOOL& bAscending)
    {
        if (!m_bShowHeader || !m_bShowSort || m_nSortColumn == NULL_COLUMN)
            return FALSE;

        nColumn = m_nSortColumn;
        bAscending = m_bSortAscending;
        return TRUE;
    }

    BOOL DragItem()
    {
        ATLASSERT(FALSE);   // must be implemented in a derived class
        return FALSE;
    }

    BOOL GroupSelect(CPoint point)
    {
        HideTitleTip();
        int nHorzScroll = GetScrollPos(SB_HORZ);
        int nVertScroll = GetScrollPos(SB_VERT);
        m_rcGroupSelect.left = min(m_ptSelectPoint.x, point.x + nHorzScroll);
        m_rcGroupSelect.right = max(m_ptSelectPoint.x, point.x + nHorzScroll);
        m_rcGroupSelect.top = min(m_ptSelectPoint.y, point.y + nVertScroll);
        m_rcGroupSelect.bottom = max(m_ptSelectPoint.y, point.y + nVertScroll);

        if (m_rcGroupSelect.IsRectEmpty())
            return FALSE;

        // select items in group
        AutoSelect(point);
        // start auto scroll timer
        SetTimer(ITEM_AUTOSCROLL_TIMER, ITEM_SCROLL_PERIOD);
        DWORD dwCurrentTick = GetTickCount();

        // timer messages are a low priority, therefore we need to simulate the timer when moving the mouse
        if ((dwCurrentTick - m_dwScrollTick) > ITEM_SCROLL_PERIOD - 10) {
            if (AutoScroll(point))
                m_dwScrollTick = dwCurrentTick;
        }

        // redraw list immediately
        return RedrawWindow();
    }

    void AutoSelect(CPoint point)
    {
        m_setSelectedItems.clear();

        if (m_rcGroupSelect.left < GetTotalWidth()) {
            int nHorzScroll = GetScrollPos(SB_HORZ);
            int nVertScroll = GetScrollPos(SB_VERT);
            CRect rcGroupSelect(m_rcGroupSelect);
            rcGroupSelect.OffsetRect(-nHorzScroll, -nVertScroll);
            int nTopItem = GetTopItem();
            int nLastItem = nTopItem + ((rcGroupSelect.bottom - (m_bShowHeader ? m_nHeaderHeight : 0)) / m_nItemHeight);
            nTopItem += ((rcGroupSelect.top - (m_bShowHeader ? m_nHeaderHeight : 0)) / m_nItemHeight) - ((rcGroupSelect.top < 0) ? 1 : 0);

            for (int nItem = nTopItem; nItem <= nLastItem; nItem++) {
                if (m_setSelectedItems.empty())
                    m_nFirstSelected = nItem;

                m_setSelectedItems.insert(nItem);
                m_nFocusItem = nItem;
                m_nFocusSubItem = NULL_SUBITEM;
            }
        }

        if (m_setSelectedItems.empty())
            m_nFirstSelected = NULL_ITEM;
    }

    BOOL AutoScroll(CPoint point)
    {
        CRect rcClient;
        GetClientRect(rcClient);
        rcClient.top = (m_bShowHeader ? m_nHeaderHeight : 0);
        int nHorzScroll = GetScrollPos(SB_HORZ);
        int nVertScroll = GetScrollPos(SB_VERT);
        BOOL bAutoScroll = FALSE;

        if (point.y < rcClient.top) {
            SendMessage(WM_VSCROLL, MAKEWPARAM(SB_LINEUP, 0));
            int nAutoScroll = GetScrollPos(SB_VERT);

            if (nVertScroll != nAutoScroll) {
                m_rcGroupSelect.top = rcClient.top + nAutoScroll - 1;
                m_rcGroupSelect.bottom = max(m_ptSelectPoint.y, point.y + nAutoScroll - 1);
                bAutoScroll = TRUE;
            }
        }

        if (point.y > rcClient.bottom) {
            SendMessage(WM_VSCROLL, MAKEWPARAM(SB_LINEDOWN, 0));
            int nAutoScroll = GetScrollPos(SB_VERT);

            if (nVertScroll != nAutoScroll) {
                m_rcGroupSelect.top = min(m_ptSelectPoint.y, point.y + nAutoScroll + 1);
                m_rcGroupSelect.bottom = rcClient.bottom + nAutoScroll + 1;
                bAutoScroll = TRUE;
            }
        }

        if (point.x < rcClient.left) {
            SendMessage(WM_HSCROLL, MAKEWPARAM(SB_LINELEFT, 0));
            int nAutoScroll = GetScrollPos(SB_HORZ);

            if (nHorzScroll != nAutoScroll) {
                m_rcGroupSelect.left = rcClient.left + nAutoScroll - 1;
                m_rcGroupSelect.right = max(m_ptSelectPoint.x, point.x + nAutoScroll - 1);
                bAutoScroll = TRUE;
            }
        }

        if (point.x > rcClient.right) {
            SendMessage(WM_HSCROLL, MAKEWPARAM(SB_LINERIGHT, 0));
            int nAutoScroll = GetScrollPos(SB_HORZ);

            if (nHorzScroll != nAutoScroll) {
                m_rcGroupSelect.left = min(m_ptSelectPoint.x, point.x + nAutoScroll + 1);
                m_rcGroupSelect.right = rcClient.right + nAutoScroll + 1;
                bAutoScroll = TRUE;
            }
        }

        // was scrolling performed?
        return bAutoScroll;
    }

    BOOL BeginScroll(int nBeginItem, int nEndItem)
    {
        T* pT = static_cast<T*>(this);

        // any scroll required?
        if (nBeginItem == nEndItem)
            return FALSE;

        // calculate scroll offset
        m_nScrollOffset = abs(nEndItem - nBeginItem) * m_nItemHeight;
        m_nScrollUnit = min(max(m_nScrollOffset / m_nItemHeight, ITEM_SCROLL_UNIT_MIN), ITEM_SCROLL_UNIT_MAX);
        m_nScrollDelta = (m_nScrollOffset - m_nScrollUnit) / m_nScrollUnit;
        m_bScrollDown = (nBeginItem < nEndItem);
        CClientDC dcClient(m_hWnd);
        CDC dcScrollList;
        dcScrollList.CreateCompatibleDC(dcClient);
        int nContextState = dcScrollList.SaveDC();
        CRect rcScrollList;
        GetClientRect(rcScrollList);
        rcScrollList.bottom = (GetCountPerPage() + abs(nEndItem - nBeginItem)) * m_nItemHeight;

        if (!m_bmpScrollList.IsNull())
            m_bmpScrollList.DeleteObject();

        m_bmpScrollList.CreateCompatibleBitmap(dcClient, rcScrollList.Width(), rcScrollList.Height());
        dcScrollList.SelectBitmap(m_bmpScrollList);
        pT->DrawBkgnd(dcScrollList.m_hDC);
        CRect rcItem;
        rcItem.left = -GetScrollPos(SB_HORZ);
        rcItem.right = GetTotalWidth();
        rcItem.top = 0;
        rcItem.bottom = rcItem.top;

        // draw all visible items into bitmap
        for (int nItem = min(nBeginItem, nEndItem); nItem < pT->GetItemCount(); rcItem.top = rcItem.bottom, nItem++) {
            rcItem.bottom = rcItem.top + m_nItemHeight;

            if (rcItem.top > rcScrollList.bottom)
                break;

            // may be implemented in a derived class
            pT->DrawItem(dcScrollList.m_hDC, nItem, rcItem);
        }

        dcScrollList.RestoreDC(nContextState);
        ScrollList();
        // start scrolling timer
        SetTimer(ITEM_SCROLL_TIMER, ITEM_SCROLL_PERIOD);
        return TRUE;
    }

    BOOL EndScroll()
    {
        KillTimer(ITEM_SCROLL_TIMER);

        if (!m_bmpScrollList.IsNull())
            m_bmpScrollList.DeleteObject();

        return Invalidate();
    }

    BOOL ScrollList()
    {
        if (m_nScrollOffset <= m_nScrollUnit)
            m_nScrollOffset--;
        else {
            m_nScrollOffset -= m_nScrollDelta;

            if (m_nScrollOffset < m_nScrollDelta)
                m_nScrollOffset = m_nScrollUnit;
        }

        if (m_bmpScrollList.IsNull() || m_nScrollOffset < 0)
            return FALSE;

        CClientDC dcClient(m_hWnd);
        CDC dcScrollList;
        dcScrollList.CreateCompatibleDC(dcClient);
        CRect rcClient;
        GetClientRect(rcClient);
        rcClient.top = (m_bShowHeader ? m_nHeaderHeight : 0);
        HBITMAP hOldBitmap = dcScrollList.SelectBitmap(m_bmpScrollList);
        CSize sizScrollBitmap;
        m_bmpScrollList.GetSize(sizScrollBitmap);
        // draw scrolled list
        dcClient.BitBlt(0, rcClient.top, rcClient.Width(), rcClient.Height(), dcScrollList, 0, m_bScrollDown ? (sizScrollBitmap.cy - (GetCountPerPage() * m_nItemHeight) - m_nScrollOffset) : m_nScrollOffset, SRCCOPY);
        dcScrollList.SelectBitmap(hOldBitmap);
        return TRUE;
    }

    BOOL EditItem(int nItem, int nSubItem = NULL_SUBITEM)
    {
        T* pT = static_cast<T*>(this);

        if (!EnsureItemVisible(nItem, nSubItem))
            return FALSE;

        if (GetFocus() != m_hWnd)
            return FALSE;

        CRect rcSubItem;

        if (!GetItemRect(nItem, nSubItem, rcSubItem))
            return FALSE;

        int nIndex = GetColumnIndex(nSubItem);

        if (pT->GetItemFlags(nItem, nIndex) & ITEM_FLAGS_READ_ONLY)
            return TRUE;

        switch (pT->GetItemFormat(nItem, nIndex)) {
        case ITEM_FORMAT_EDIT:
            m_bEditItem = TRUE;

            if (!RedrawWindow())
                return FALSE;

            if (!m_wndItemEdit.Create(m_hWnd, nItem, nSubItem, rcSubItem, pT->GetItemFlags(nItem, nIndex), pT->GetItemText(nItem, nIndex)))
                return FALSE;

            break;

        case ITEM_FORMAT_DATETIME:          {
            m_bEditItem = TRUE;

            if (!RedrawWindow())
                return FALSE;

            SYSTEMTIME stItemDate;
            GetItemDate(nItem, nIndex, stItemDate);

            if (!m_wndItemDate.Create(m_hWnd, nItem, nSubItem, rcSubItem, pT->GetItemFlags(nItem, nIndex), stItemDate))
                return FALSE;
        }
        break;

        case ITEM_FORMAT_COMBO:             {
            m_bEditItem = TRUE;

            if (!RedrawWindow())
                return FALSE;

            CListArray < CString > aComboList;

            if (!pT->GetItemComboList(nItem, nIndex, aComboList))
                return FALSE;

            if (!m_wndItemCombo.Create(m_hWnd, nItem, nSubItem, rcSubItem, pT->GetItemFlags(nItem, nIndex), pT->GetItemText(nItem, nIndex), m_bShowThemed, aComboList))
                return FALSE;
        }
        break;
        }

        return TRUE;
    }

    CString FormatDate(SYSTEMTIME& stFormatDate)
    {
        if (stFormatDate.wYear == 0)
            return _T("");

        // format date to local format
        TCHAR szDateFormat[ DATE_STRING ];
        return GetDateFormat(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &stFormatDate, NULL, szDateFormat, DATE_STRING) == 0 ? _T("") : szDateFormat;
    }

    CString FormatTime(SYSTEMTIME& stFormatDate)
    {
        SYSTEMTIME stFormatTime = stFormatDate;
        stFormatTime.wYear = 0;
        stFormatTime.wMonth = 0;
        stFormatTime.wDay = 0;
        // format time to local format
        TCHAR szTimeFormat[ DATE_STRING ];
        return GetTimeFormat(LOCALE_USER_DEFAULT, 0, &stFormatTime, NULL, szTimeFormat, DATE_STRING) == 0 ? _T("") : szTimeFormat;
    }

    void NotifyParent(int nItem, int nSubItem, int nMessage)
    {
        T* pT = static_cast<T*>(this);
        CListNotify listNotify;
        listNotify.m_hdrNotify.hwndFrom = pT->m_hWnd;
        listNotify.m_hdrNotify.idFrom = pT->GetDlgCtrlID();
        listNotify.m_hdrNotify.code = nMessage;
        listNotify.m_nItem = nItem;
        listNotify.m_nSubItem = GetColumnIndex(nSubItem);
        listNotify.m_nExitChar = 0;
        listNotify.m_lpszItemText = NULL;
        listNotify.m_lpItemDate = NULL;
        // forward notification to parent
        FORWARD_WM_NOTIFY(pT->GetParent(), listNotify.m_hdrNotify.idFrom, &listNotify.m_hdrNotify, ::SendMessage);
    }

    BOOL ShowTitleTip(CPoint point, int nItem, int nSubItem)
    {
        T* pT = static_cast<T*>(this);

        // do not show titletip if editing
        if (m_bEditItem)
            return FALSE;

        // is titletip already shown for this item?
        if (nItem == m_nTitleTipItem && nSubItem == m_nTitleTipSubItem)
            return FALSE;

        CRect rcSubItem;

        if (!GetItemRect(nItem, nSubItem, rcSubItem)) {
            HideTitleTip();
            return FALSE;
        }

        int nIndex = GetColumnIndex(nSubItem);
        CRect rcItemText(rcSubItem);
        // margin item text
        rcItemText.left += nSubItem == 0 ? 4 : 3;
        rcItemText.DeflateRect(4, 0);

        // offset item text (for image)
        if (!m_ilItemImages.IsNull() && pT->GetItemImage(nItem, nIndex) != ITEM_IMAGE_NONE) {
            CSize sizeIcon;
            m_ilItemImages.GetIconSize(sizeIcon);
            rcItemText.left += sizeIcon.cx + 4;
        }

        // is current cursor position over item text (not over an icon)?
        if (!rcItemText.PtInRect(point))
            return FALSE;

        CString strItemText;

        switch (pT->GetItemFormat(nItem, nIndex)) {
        case ITEM_FORMAT_CHECKBOX:
        case ITEM_FORMAT_CHECKBOX_3STATE:
        case ITEM_FORMAT_PROGRESS:
            break; // no titletip for checkboxes or progress

        case ITEM_FORMAT_DATETIME:          {
            SYSTEMTIME stItemDate;

            if (!GetItemDate(nItem, nIndex, stItemDate))
                break;

            UINT nItemFlags = pT->GetItemFlags(nItem, nIndex);

            if (nItemFlags & ITEM_FLAGS_DATE_ONLY)
                strItemText = FormatDate(stItemDate);
            else if (nItemFlags & ITEM_FLAGS_TIME_ONLY)
                strItemText = FormatTime(stItemDate);
            else
                strItemText = FormatDate(stItemDate) + _T(" ") + FormatTime(stItemDate);
        }
        break;

        default:
            strItemText = pT->GetItemText(nItem, nIndex);
            break;
        }

        if (strItemText.IsEmpty()) {
            HideTitleTip();
            return FALSE;
        }

        ClientToScreen(rcItemText);

        if (!m_wndTitleTip.Show(rcItemText, strItemText, pT->GetItemToolTip(nItem, nIndex))) {
            HideTitleTip();
            return FALSE;
        }

        m_nTitleTipItem = nItem;
        m_nTitleTipSubItem = nSubItem;
        return TRUE;
    }

    BOOL HideTitleTip(BOOL bResetItem = TRUE)
    {
        if (bResetItem) {
            m_nTitleTipItem = NULL_ITEM;
            m_nTitleTipSubItem = NULL_SUBITEM;
        }

        return m_wndTitleTip.Hide();
    }

    BEGIN_MSG_MAP_EX(CListImpl)
    MSG_WM_CREATE(OnCreate)
    MSG_WM_DESTROY(OnDestroy)
    MSG_WM_SETFOCUS(OnSetFocus)
    MSG_WM_KILLFOCUS(OnKillFocus)
    MSG_WM_GETDLGCODE(OnGetDlgCode)
    MSG_WM_SIZE(OnSize)
    MSG_WM_HSCROLL(OnHScroll)
    MSG_WM_VSCROLL(OnVScroll)
    MSG_WM_CANCELMODE(OnCancelMode)
    MESSAGE_RANGE_HANDLER_EX(WM_MOUSEFIRST, WM_MOUSELAST, OnMouseRange)
    MSG_WM_LBUTTONDOWN(OnLButtonDown)
    MSG_WM_LBUTTONUP(OnLButtonUp)
    MSG_WM_LBUTTONDBLCLK(OnLButtonDblClk)
    MSG_WM_RBUTTONDOWN(OnRButtonDown)
    MSG_WM_RBUTTONUP(OnRButtonUp)
    MSG_WM_MOUSEMOVE(OnMouseMove)
    MSG_WM_MOUSELEAVE(OnMouseLeave)
    MSG_WM_MOUSEWHEEL(OnMouseWheel)
    MSG_WM_TIMER(OnTimer)
    MSG_WM_KEYDOWN(OnKeyDown)
    MSG_WM_SYSKEYDOWN(OnSysKeyDown)
    MSG_WM_SETTINGCHANGE(OnSettingsChange)
    MSG_WM_SYSCOLORCHANGE(OnSettingsChange)
    MSG_WM_FONTCHANGE(OnSettingsChange)
    MSG_WM_THEMECHANGED(OnSettingsChange)
    NOTIFY_CODE_HANDLER_EX(LCN_ENDEDIT, OnEndEdit)
    MESSAGE_HANDLER(WM_CTLCOLORLISTBOX, OnCtlColorListBox)
    MESSAGE_HANDLER(WM_CTLCOLOREDIT, OnCtlColorListBox)
    CHAIN_MSG_MAP(CDoubleBufferImpl< CListImpl >)
    REFLECT_NOTIFICATIONS()
    END_MSG_MAP_EX()

    int OnCreate(LPCREATESTRUCT lpCreateStruct)
    {
        T* pT = static_cast<T*>(this);
        return pT->Initialise() ? 0 : -1;
    }

    void OnDestroy()
    {
        m_oleDragDrop.Revoke();

        if (m_wndDropArrows.IsWindow())
            m_wndDropArrows.DestroyWindow();

        if (m_wndTitleTip.IsWindow())
            m_wndTitleTip.DestroyWindow();

        if (m_wndItemEdit.IsWindow())
            m_wndItemEdit.DestroyWindow();

        if (m_wndItemCombo.IsWindow())
            m_wndItemCombo.DestroyWindow();

        if (m_wndItemDate.IsWindow())
            m_wndItemDate.DestroyWindow();

        if (m_ttToolTip.IsWindow())
            m_ttToolTip.DestroyWindow();
    }

    void OnSetFocus(HWND hOldWnd)
    {
        Invalidate();
    }

    void OnKillFocus(HWND hNewWnd)
    {
        Invalidate();
    }

    UINT OnGetDlgCode(LPMSG lpMessage)
    {
        return DLGC_WANTARROWS | DLGC_WANTTAB | DLGC_WANTCHARS;
    }

    void OnSize(UINT nType, CSize size)
    {
        // stop any pending scroll
        EndScroll();

        // end any pending edit
        if (m_bEditItem)
            SetFocus();

        ResetScrollBars(SB_BOTH, -1, FALSE);
        Invalidate();
    }

    void OnHScroll(int nSBCode, short nPos, HWND hScrollBar)
    {
        // stop any pending scroll
        EndScroll();

        // end any pending edit
        if (m_bEditItem)
            SetFocus();

        HideTitleTip();
        CRect rcClient;
        GetClientRect(rcClient);
        int nScrollPos = GetScrollPos(SB_HORZ);

        switch (nSBCode) {
        case SB_LEFT:
            nScrollPos = 0;
            break;

        case SB_LINELEFT:
            nScrollPos = max(nScrollPos - ITEM_SCROLL_OFFSET, 0);
            break;

        case SB_PAGELEFT:
            nScrollPos = max(nScrollPos - rcClient.Width(), 0);
            break;

        case SB_RIGHT:
            nScrollPos = rcClient.Width();
            break;

        case SB_LINERIGHT:
            nScrollPos = min(nScrollPos + ITEM_SCROLL_OFFSET, GetTotalWidth());
            break;

        case SB_PAGERIGHT:
            nScrollPos = min(nScrollPos + rcClient.Width(), GetTotalWidth());
            break;

        case SB_THUMBPOSITION:
        case SB_THUMBTRACK:     {
            SCROLLINFO infoScroll;
            ZeroMemory(&infoScroll, sizeof(SCROLLINFO));
            infoScroll.cbSize = sizeof(SCROLLINFO);
            infoScroll.fMask = SIF_TRACKPOS;

            // get 32-bit scroll position
            if (!GetScrollInfo(SB_HORZ, &infoScroll))
                return;

            // has scroll position changed?
            if (infoScroll.nTrackPos == nScrollPos)
                return;

            nScrollPos = infoScroll.nTrackPos;
        }
        break;

        default:
            return;
        }

        ResetScrollBars(SB_HORZ, nScrollPos, FALSE);
        Invalidate();
    }

    void OnVScroll(int nSBCode, short nPos, HWND hScrollBar)
    {
        T* pT = static_cast<T*>(this);

        // end any pending edit
        if (m_bEditItem)
            SetFocus();

        HideTitleTip();
        CRect rcClient;
        GetClientRect(rcClient);
        rcClient.top = (m_bShowHeader ? m_nHeaderHeight : 0);
        int nScrollPos = GetScrollPos(SB_VERT);
        BOOL bScrollList = m_bSmoothScroll;

        switch (nSBCode) {
        case SB_TOP:
            nScrollPos = 0;
            bScrollList = FALSE;
            break;

        case SB_LINEUP:
            nScrollPos = max(nScrollPos - m_nItemHeight, 0);
            break;

        case SB_PAGEUP:
            nScrollPos = max(nScrollPos - rcClient.Height(), 0);
            break;

        case SB_BOTTOM:
            nScrollPos = pT->GetItemCount() * m_nItemHeight;
            bScrollList = FALSE;
            break;

        case SB_LINEDOWN:
            nScrollPos += m_nItemHeight;
            break;

        case SB_PAGEDOWN:
            nScrollPos += rcClient.Height();
            break;

        case SB_THUMBTRACK:
        case SB_THUMBPOSITION:  {
            SCROLLINFO infoScroll;
            ZeroMemory(&infoScroll, sizeof(SCROLLINFO));
            infoScroll.cbSize = sizeof(SCROLLINFO);
            infoScroll.fMask = SIF_TRACKPOS;

            // get 32-bit scroll position
            if (!GetScrollInfo(SB_VERT, &infoScroll))
                return;

            // has scroll position changed?
            if (infoScroll.nTrackPos == nScrollPos)
                return;

            nScrollPos = infoScroll.nTrackPos;
            bScrollList = FALSE;
        }
        break;

        case SB_ENDSCROLL:
            m_bScrolling = FALSE;

        default:
            return;
        }

        // store original top item before scrolling
        int nTopItem = GetTopItem();
        ResetScrollBars(SB_VERT, nScrollPos, FALSE);

        if (bScrollList && !m_bScrolling)
            m_bScrolling = BeginScroll(nTopItem, GetTopItem());
        else
            EndScroll();
    }

    void OnCancelMode()
    {
        if (m_bButtonDown)
            ReleaseCapture();

        HideTitleTip();
        m_wndDropArrows.Hide();
        m_nDragColumn = NULL_COLUMN;
        m_nHighlightColumn = NULL_COLUMN;
    }

    LRESULT OnMouseRange(UINT nMessage, WPARAM wParam, LPARAM lParam)
    {
        if (m_ttToolTip.IsWindow()) {
            MSG msgRelay = { m_hWnd, nMessage, wParam, lParam };
            m_ttToolTip.RelayEvent(&msgRelay);
        }

        SetMsgHandled(FALSE);
        return 0;
    }

    void OnLButtonDown(UINT nFlags, CPoint point)
    {
        T* pT = static_cast<T*>(this);
        HideTitleTip(FALSE);
        m_bButtonDown = TRUE;
        m_ptDownPoint = point;
        m_ptSelectPoint = CPoint(point.x + GetScrollPos(SB_HORZ), point.y + GetScrollPos(SB_VERT));
        // stop any pending scroll
        EndScroll();
        SetFocus();
        // capture all mouse input
        SetCapture();
        int nColumn = NULL_COLUMN;
        UINT nHeaderFlags = HITTEST_FLAG_NONE;

        // are we over the header?
        if (HitTestHeader(point, nColumn, nHeaderFlags)) {
            CListColumn listColumn;

            if (!GetColumn(nColumn, listColumn))
                return;

            if (!listColumn.m_bFixed && (nHeaderFlags & HITTEST_FLAG_HEADER_DIVIDER)) {
                SetCursor(m_curDivider);
                // begin column resizing
                m_bColumnSizing = TRUE;
                m_nColumnSizing = nColumn;
                m_nStartSize = listColumn.m_nWidth;
                m_nStartPos = GET_X_LPARAM(GetMessagePos());
            } else {
                m_nHighlightColumn = nColumn;
                InvalidateHeader();
            }

            return;
        }

        int nItem = NULL_ITEM;
        int nSubItem = NULL_SUBITEM;

        if (!HitTest(point, nItem, nSubItem)) {
            m_nFirstSelected = NULL_ITEM;
            m_bBeginSelect = TRUE;
        } else {
            // do not begin group select from first columns
            if (!(nFlags & MK_SHIFT) && !(nFlags & MK_CONTROL) && nSubItem != 0) {
                m_bBeginSelect = TRUE;
                m_nFirstSelected = nItem;
            }

            // only select item if not already selected
            if ((nFlags & MK_SHIFT) || (nFlags & MK_CONTROL) || !IsSelected(nItem) || m_setSelectedItems.size() <= 1)
                SelectItem(nItem, nSubItem, nFlags);

            int nIndex = GetColumnIndex(nSubItem);

            if (!(pT->GetItemFlags(nItem, nIndex) & ITEM_FLAGS_READ_ONLY)) {
                switch (pT->GetItemFormat(nItem, nIndex)) {
                case ITEM_FORMAT_CHECKBOX:
                    m_bBeginSelect = FALSE;
                    pT->SetItemText(nItem, nIndex, _ttoi(pT->GetItemText(nItem, nIndex)) > 0 ? _T("0") : _T("1"));
                    NotifyParent(nItem, nSubItem, LCN_MODIFIED);
                    InvalidateItem(nItem);
                    break;

                case ITEM_FORMAT_CHECKBOX_3STATE:   {
                    m_bBeginSelect = FALSE;
                    int nCheckImage = _ttoi(pT->GetItemText(nItem, nIndex));

                    if (nCheckImage < 0)
                        pT->SetItemText(nItem, nIndex, _T("0"));
                    else if (nCheckImage > 0)
                        pT->SetItemText(nItem, nIndex, _T("-1"));
                    else
                        pT->SetItemText(nItem, nIndex, _T("1"));

                    NotifyParent(nItem, nSubItem, LCN_MODIFIED);
                    InvalidateItem(nItem);
                }
                break;

                case ITEM_FORMAT_HYPERLINK:
                    m_bBeginSelect = FALSE;
                    SetCursor(m_curHyperLink);
                    NotifyParent(nItem, nSubItem, LCN_HYPERLINK);
                    break;
                }
            }
        }
    }

    void OnLButtonUp(UINT nFlags, CPoint point)
    {
        if (m_bButtonDown)
            ReleaseCapture();

        // finish resizing or selecting a column
        if (m_bColumnSizing || m_nHighlightColumn != NULL_COLUMN) {
            // are we changing the sort order?
            if (!m_bColumnSizing && m_nHighlightColumn != NULL_COLUMN)
                SortColumn(m_nHighlightColumn);

            m_bColumnSizing = FALSE;
            m_nColumnSizing = NULL_COLUMN;
            m_nHighlightColumn = NULL_COLUMN;
            m_nStartSize = 0;
            m_nStartPos = 0;
            InvalidateHeader();
        }

        m_bBeginSelect = FALSE;
        m_bButtonDown = FALSE;
        m_ptDownPoint = 0;
        m_ptSelectPoint = 0;

        // have we finished a group select?
        if (m_bGroupSelect) {
            m_bGroupSelect = FALSE;
            Invalidate();
        } else {
            int nItem = NULL_ITEM;
            int nSubItem = NULL_SUBITEM;

            // de-select item if current item is selected
            if (HitTest(point, nItem, nSubItem) && IsSelected(nItem) && m_setSelectedItems.size() > 1 && !(nFlags & MK_SHIFT) && !(nFlags & MK_CONTROL))
                SelectItem(nItem, nSubItem, nFlags);

            // notify parent of left-click item
            NotifyParent(nItem, nSubItem, LCN_LEFTCLICK);
        }
    }

    void OnLButtonDblClk(UINT nFlags, CPoint point)
    {
        HideTitleTip(FALSE);
        // handle double-clicks (for drawing)
        SendMessage(WM_LBUTTONDOWN, 0, MAKELPARAM(point.x, point.y));
        int nColumn = NULL_COLUMN;
        UINT nHeaderFlags = HITTEST_FLAG_NONE;

        // resize column if double-click on a divider
        if (HitTestHeader(point, nColumn, nHeaderFlags) && (nHeaderFlags & HITTEST_FLAG_HEADER_DIVIDER))
            AutoSizeColumn(nColumn);

        int nItem = NULL_ITEM;
        int nSubItem = NULL_SUBITEM;
        HitTest(point, nItem, nSubItem);
        // notify parent of double-clicked item
        NotifyParent(nItem, nSubItem, LCN_DBLCLICK);
    }

    void OnRButtonDown(UINT nFlags, CPoint point)
    {
        // stop any pending scroll
        EndScroll();
        SetFocus();
        HideTitleTip(FALSE);
        int nItem = NULL_ITEM;
        int nSubItem = NULL_SUBITEM;

        // only select item if not already selected (de-select in OnLButtonUp)
        if (HitTest(point, nItem, nSubItem) && !IsSelected(nItem))
            SelectItem(nItem, nSubItem, nFlags);
    }

    void OnRButtonUp(UINT nFlags, CPoint point)
    {
        int nItem = NULL_ITEM;
        int nSubItem = NULL_SUBITEM;

        if (!HitTest(point, nItem, nSubItem))
            ResetSelected();

        // notify parent of right-click item
        NotifyParent(nItem, nSubItem, LCN_RIGHTCLICK);
    }

    void OnMouseMove(UINT nFlags, CPoint point)
    {
        T* pT = static_cast<T*>(this);

        if (!(nFlags & MK_LBUTTON)) {
            if (m_bButtonDown)
                ReleaseCapture();

            m_bButtonDown = FALSE;
        }

        if (!m_bMouseOver) {
            m_bMouseOver = TRUE;
            TRACKMOUSEEVENT trkMouse;
            trkMouse.cbSize = sizeof(TRACKMOUSEEVENT);
            trkMouse.dwFlags = TME_LEAVE;
            trkMouse.hwndTrack = m_hWnd;
            // notify when the mouse leaves button
            _TrackMouseEvent(&trkMouse);
        }

        if (m_bButtonDown) {
            // are we resizing a column?
            if (m_bColumnSizing) {
                ResizeColumn();
                return;
            }

            // are we beginning to drag a column?
            if (m_nHighlightColumn != NULL_COLUMN && (point.x < m_ptDownPoint.x - DRAG_HEADER_OFFSET || point.x > m_ptDownPoint.x + DRAG_HEADER_OFFSET || point.y < m_ptDownPoint.y - DRAG_HEADER_OFFSET || point.y > m_ptDownPoint.y + DRAG_HEADER_OFFSET)) {
                DragColumn();
                return;
            }

            // are we beginning a group select or dragging an item?
            if (point.x < m_ptDownPoint.x - DRAG_ITEM_OFFSET || point.x > m_ptDownPoint.x + DRAG_ITEM_OFFSET || point.y < m_ptDownPoint.y - DRAG_ITEM_OFFSET || point.y > m_ptDownPoint.y + DRAG_ITEM_OFFSET) {
                if (m_bBeginSelect || !m_bDragDrop)
                    m_bGroupSelect = (!m_bSingleSelect && !m_bEditItem);
                else {
                    int nItem = NULL_ITEM;
                    int nSubItem = NULL_SUBITEM;

                    if (HitTest(point, nItem, nSubItem)) {
                        // select the drag item (if not already selected)
                        if (!IsSelected(nItem))
                            SelectItem(nItem, nSubItem, nFlags);

                        // begin drag item operation
                        pT->DragItem();
                    }
                }
            }

            if (m_bGroupSelect) {
                GroupSelect(point);
                return;
            }
        } else {
            int nColumn = NULL_COLUMN;
            UINT nHeaderFlags = HITTEST_FLAG_NONE;
            // are we over the header?
            BOOL bHitTestHeader = HitTestHeader(point, nColumn, nHeaderFlags);

            if (m_bShowThemed && !m_thmHeader.IsThemeNull()) {
                if (bHitTestHeader && m_nHotColumn != nColumn) {
                    m_nHotColumn = nColumn;
                    InvalidateHeader();
                } else if (!bHitTestHeader && m_nHotColumn != NULL_COLUMN) {
                    m_nHotColumn = NULL_COLUMN;
                    InvalidateHeader();
                }
            }

            if (bHitTestHeader) {
                HideTitleTip();
                CListColumn listColumn;

                if (GetColumn(nColumn, listColumn) && !listColumn.m_bFixed && (nHeaderFlags & HITTEST_FLAG_HEADER_DIVIDER))
                    SetCursor(m_curDivider);

                return;
            }

            int nItem = NULL_ITEM;
            int nSubItem = NULL_SUBITEM;

            if (!HitTest(point, nItem, nSubItem)) {
                if (m_nHotItem != NULL_ITEM && m_nHotSubItem != NULL_SUBITEM) {
                    // redraw old hot item
                    int nIndex = GetColumnIndex(m_nHotSubItem);

                    if (pT->GetItemFormat(m_nHotItem, nIndex) == ITEM_FORMAT_HYPERLINK && !(pT->GetItemFlags(m_nHotItem, nIndex) & ITEM_FLAGS_READ_ONLY))
                        InvalidateItem(m_nHotItem, m_nHotSubItem);
                }

                m_ttToolTip.Activate(FALSE);
                m_ttToolTip.DelTool(m_hWnd, TOOLTIP_TOOL_ID);
                m_nHotItem = NULL_ITEM;
                m_nHotSubItem = NULL_SUBITEM;
                HideTitleTip();
            } else {
                // has the hot item changed?
                if (nItem != m_nHotItem || nSubItem != m_nHotSubItem) {
                    // redraw old hot item
                    int nIndex = GetColumnIndex(m_nHotSubItem);

                    if (pT->GetItemFormat(m_nHotItem, nIndex) == ITEM_FORMAT_HYPERLINK && !(pT->GetItemFlags(m_nHotItem, nIndex) & ITEM_FLAGS_READ_ONLY))
                        InvalidateItem(m_nHotItem, m_nHotSubItem);

                    m_nHotItem = nItem;
                    m_nHotSubItem = nSubItem;
                }

                int nIndex = GetColumnIndex(m_nHotSubItem);
                UINT nItemFormat = pT->GetItemFormat(m_nHotItem, nIndex);
                UINT nItemFlags = pT->GetItemFlags(m_nHotItem, nIndex);

                // draw new hot hyperlink item
                if (nItemFormat == ITEM_FORMAT_HYPERLINK && !(nItemFlags & ITEM_FLAGS_READ_ONLY)) {
                    InvalidateItem(m_nHotItem, m_nHotSubItem);
                    SetCursor(m_curHyperLink);
                }

                // get tooltip for this item
                CString strToolTip = pT->GetItemToolTip(m_nHotItem, nIndex);
                CRect rcSubItem;

                if (!strToolTip.IsEmpty() && GetItemRect(m_nHotItem, rcSubItem)) {
                    m_ttToolTip.Activate(TRUE);
                    m_ttToolTip.AddTool(m_hWnd, (LPCTSTR)strToolTip.Left(SHRT_MAX), rcSubItem, TOOLTIP_TOOL_ID);
                } else {
                    m_ttToolTip.Activate(FALSE);
                    m_ttToolTip.DelTool(m_hWnd, TOOLTIP_TOOL_ID);
                }

                // show titletips for this item
                ShowTitleTip(point, m_nHotItem, m_nHotSubItem);
            }
        }
    }

    void OnMouseLeave()
    {
        m_bMouseOver = FALSE;

        if (m_nHotColumn != NULL_COLUMN) {
            m_nHotColumn = NULL_COLUMN;
            InvalidateHeader();
        }

        if (m_nHotItem != NULL_ITEM || m_nHotSubItem != NULL_SUBITEM) {
            m_nHotItem = NULL_ITEM;
            m_nHotSubItem = NULL_SUBITEM;
            Invalidate();
        }
    }

    BOOL OnMouseWheel(UINT nFlags, short nDelta, CPoint point)
    {
        HideTitleTip();

        // end any pending edit
        if (m_bEditItem)
            SetFocus();

        int nRowsScrolled = m_nMouseWheelScroll * nDelta / 120;
        int nScrollPos = GetScrollPos(SB_VERT);

        if (nRowsScrolled > 0)
            nScrollPos = max(nScrollPos - (nRowsScrolled * m_nItemHeight), 0);
        else
            nScrollPos += (-nRowsScrolled * m_nItemHeight);

        ResetScrollBars(SB_VERT, nScrollPos, FALSE);
        Invalidate();
        return TRUE;
    }

    void OnTimer(UINT nIDEvent)
    {
        switch (nIDEvent) {
        case RESIZE_COLUMN_TIMER:
            ResizeColumn(TRUE);
            break;

        case ITEM_VISIBLE_TIMER:    {
            KillTimer(ITEM_VISIBLE_TIMER);
            int nFocusItem = NULL_ITEM;
            int nFocusSubItem = NULL_SUBITEM;

            // get current focus item
            if (!GetFocusItem(nFocusItem, nFocusSubItem))
                break;

            // make sure current focus item is visible before editing
            if (!EditItem(nFocusItem, nFocusSubItem))
                break;
        }
        break;

        case ITEM_AUTOSCROLL_TIMER:
            if (!m_bGroupSelect)
                KillTimer(ITEM_AUTOSCROLL_TIMER);
            else {
                DWORD dwPoint = GetMessagePos();
                CPoint ptMouse(GET_X_LPARAM(dwPoint), GET_Y_LPARAM(dwPoint));
                ScreenToClient(&ptMouse);
                // automatically scroll when group selecting
                AutoScroll(ptMouse);
                AutoSelect(ptMouse);
            }

            break;

        case ITEM_SCROLL_TIMER:
            if (!ScrollList())
                EndScroll();

            break;
        }
    }

    void OnKeyDown(TCHAR nChar, UINT nRepCnt, UINT nFlags)
    {
        T* pT = static_cast<T*>(this);
        // stop any pending scroll
        EndScroll();
        BOOL bCtrlKey = ((GetKeyState(VK_CONTROL) & 0x8000) != 0);
        BOOL bShiftKey = ((GetKeyState(VK_SHIFT) & 0x8000) != 0);
        CRect rcClient;
        GetClientRect(rcClient);
        int nFocusItem = NULL_ITEM;
        int nFocusSubItem = NULL_SUBITEM;
        GetFocusItem(nFocusItem, nFocusSubItem);

        switch (nChar) {
        case VK_DOWN:
            SetFocusItem(min(nFocusItem + 1, pT->GetItemCount() - 1), nFocusSubItem);
            break;

        case VK_UP:
            SetFocusItem(max(nFocusItem - 1, 0), nFocusSubItem);
            break;

        case VK_NEXT:
            SetFocusItem(min(nFocusItem + GetCountPerPage(FALSE) - 1, pT->GetItemCount() - 1), nFocusSubItem);
            break;

        case VK_PRIOR:
            SetFocusItem(max(nFocusItem - GetCountPerPage(FALSE) + 1, 0), nFocusSubItem);
            break;

        case VK_HOME:
            SetFocusItem(0, nFocusSubItem);
            break;

        case VK_END:
            SetFocusItem(pT->GetItemCount() - 1, nFocusSubItem);
            break;

        case VK_LEFT:
            if (m_bFocusSubItem)
                SetFocusItem(nFocusItem, max(nFocusSubItem - 1, 0));
            else
                SetScrollPos(SB_HORZ, max(GetScrollPos(SB_HORZ) - (bCtrlKey ? ITEM_SCROLL_OFFSET * 10 : ITEM_SCROLL_OFFSET), 0));

            break;

        case VK_RIGHT:
            if (m_bFocusSubItem)
                SetFocusItem(nFocusItem, min(nFocusSubItem + 1, GetColumnCount() - 1));
            else
                SetScrollPos(SB_HORZ, min(GetScrollPos(SB_HORZ) + (bCtrlKey ? ITEM_SCROLL_OFFSET * 10 : ITEM_SCROLL_OFFSET), rcClient.Width()));

            break;

        case VK_TAB:
            if (!bCtrlKey && m_bFocusSubItem)
                SetFocusItem(nFocusItem, bShiftKey ? max(nFocusSubItem - 1, 0) : min(nFocusSubItem + 1, GetColumnCount() - 1));

            break;

        default:
            if (nChar == VK_SPACE) {
                int nIndex = GetColumnIndex(nFocusSubItem);

                if (!(pT->GetItemFlags(nFocusItem, nIndex) & ITEM_FLAGS_READ_ONLY)) {
                    switch (pT->GetItemFormat(nFocusItem, nIndex)) {
                    case ITEM_FORMAT_CHECKBOX:
                        pT->SetItemText(nFocusItem, nIndex, _ttoi(pT->GetItemText(nFocusItem, nIndex)) > 0 ? _T("0") : _T("1"));
                        NotifyParent(nFocusItem, nFocusSubItem, LCN_MODIFIED);
                        InvalidateItem(nFocusItem);
                        return;

                    case ITEM_FORMAT_CHECKBOX_3STATE:   {
                        int nCheckImage = _ttoi(pT->GetItemText(nFocusItem, nIndex));

                        if (nCheckImage < 0)
                            pT->SetItemText(nFocusItem, nIndex, _T("0"));
                        else if (nCheckImage > 0)
                            pT->SetItemText(nFocusItem, nIndex, _T("-1"));
                        else
                            pT->SetItemText(nFocusItem, nIndex, _T("1"));

                        NotifyParent(nFocusItem, nFocusSubItem, LCN_MODIFIED);
                        InvalidateItem(nFocusItem);
                    }

                    return;
                    }
                }
            }

            if (bCtrlKey && nChar == _T('A') && !m_bSingleSelect) {
                m_setSelectedItems.clear();

                for (int nItem = 0; nItem < pT->GetItemCount(); nItem++)
                    m_setSelectedItems.insert(nItem);

                Invalidate();
                return;
            }

            if (!bCtrlKey && iswprint(nChar) && iswupper(nChar)) {
                int nSortIndex = GetColumnIndex(m_nSortColumn);
                int nStartItem = nFocusItem + 1;
                DWORD dwCurrentTick = GetTickCount();
                CString strStart;
                strStart += nChar;

                // has there been another keypress since last search period?
                if ((dwCurrentTick - m_dwSearchTick) < SEARCH_PERIOD) {
                    if (m_strSearchString.Left(1) != strStart)
                        m_strSearchString += nChar;

                    CString strFocusText = pT->GetItemText(nFocusItem, nSortIndex);

                    // are we continuing to type characters under current focus item?
                    if (m_strSearchString.GetLength() > 1 && m_strSearchString.CompareNoCase(strFocusText.Left(m_strSearchString.GetLength())) == 0) {
                        m_dwSearchTick = GetTickCount();
                        return;
                    }
                } else {
                    if (m_strSearchString.Left(1) != strStart)
                        nStartItem = 0;

                    m_strSearchString = strStart;
                }

                m_dwSearchTick = GetTickCount();

                // scan for next search string
                for (int nFirst = nStartItem; nFirst < pT->GetItemCount(); nFirst++) {
                    CString strItemText = pT->GetItemText(nFirst, nSortIndex);

                    if (m_strSearchString.CompareNoCase(strItemText.Left(m_strSearchString.GetLength())) == 0) {
                        SelectItem(nFirst, nFocusSubItem, TRUE);
                        EnsureItemVisible(nFirst, nFocusSubItem);
                        return;
                    }
                }

                // re-scan from top if not found search string
                for (int nSecond = 0; nSecond < pT->GetItemCount(); nSecond++) {
                    CString strItemText = pT->GetItemText(nSecond, nSortIndex);

                    if (m_strSearchString.CompareNoCase(strItemText.Left(m_strSearchString.GetLength())) == 0) {
                        SelectItem(nSecond, nFocusSubItem, TRUE);
                        EnsureItemVisible(nSecond, nFocusSubItem);
                        return;
                    }
                }
            }

            return;
        }

        if (!bCtrlKey)
            SelectItem(m_nFocusItem, m_nFocusSubItem, bShiftKey ? MK_SHIFT : 0);
    }

    void OnSysKeyDown(TCHAR nChar, UINT nRepCnt, UINT nFlags)
    {
        HideTitleTip(FALSE);
        SetMsgHandled(FALSE);
    }

    void OnSettingsChange(UINT nFlags, LPCTSTR lpszSection)
    {
        OnSettingsChange();
    }

    void OnSettingsChange()
    {
        LoadSettings();
        ResetScrollBars();
        Invalidate();
    }

    LRESULT OnCtlColorListBox(UINT nMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
        return DefWindowProc(nMsg, wParam, lParam);
    }

    LRESULT OnEndEdit(LPNMHDR lpNMHDR)
    {
        T* pT = static_cast<T*>(this);
        CListNotify *pListNotify = reinterpret_cast<CListNotify *>(lpNMHDR);
        m_bEditItem = FALSE;
        int nIndex = GetColumnIndex(pListNotify->m_nSubItem);

        switch (pListNotify->m_nExitChar) {
        case VK_ESCAPE:
            break; // do nothing

        case VK_DELETE:
            pT->SetItemText(pListNotify->m_nItem, nIndex, _T(""));
            NotifyParent(pListNotify->m_nItem, pListNotify->m_nSubItem, LCN_MODIFIED);
            break;

        default:
            if (pListNotify->m_lpItemDate == NULL)
                pT->SetItemText(pListNotify->m_nItem, nIndex, pListNotify->m_lpszItemText);
            else {
                if (_ttoi(pListNotify->m_lpszItemText) == 0)
                    pT->SetItemText(pListNotify->m_nItem, nIndex, _T(""));
                else
                    pT->SetItemDate(pListNotify->m_nItem, nIndex, *pListNotify->m_lpItemDate);
            }

            if (pListNotify->m_nExitChar == VK_TAB)
                PostMessage(WM_KEYDOWN, (WPARAM)VK_TAB);

            NotifyParent(pListNotify->m_nItem, pListNotify->m_nSubItem, LCN_MODIFIED);
            break;
        }

        InvalidateItem(pListNotify->m_nItem);
        return 0;
    }

    DWORD OnDragEnter(FORMATETC& FormatEtc, STGMEDIUM& StgMedium, DWORD dwKeyState, CPoint point)
    {
        DWORD dwEffect = DROPEFFECT_NONE;

        if (FormatEtc.cfFormat == m_nHeaderClipboardFormat) {
            LPBYTE lpDragHeader = (LPBYTE)GlobalLock(StgMedium.hGlobal);

            if (lpDragHeader == NULL)
                return DROPEFFECT_NONE;

            // dragged column must originate from this control
            if (*((HWND*)lpDragHeader) == m_hWnd)
                dwEffect = DropColumn(point) ? DROPEFFECT_MOVE : DROPEFFECT_NONE;

            GlobalUnlock(StgMedium.hGlobal);
        }

        return dwEffect;
    }

    DWORD OnDragOver(FORMATETC& FormatEtc, STGMEDIUM& StgMedium, DWORD dwKeyState, CPoint point)
    {
        DWORD dwEffect = DROPEFFECT_NONE;

        if (FormatEtc.cfFormat == m_nHeaderClipboardFormat) {
            LPBYTE lpDragHeader = (LPBYTE)GlobalLock(StgMedium.hGlobal);

            if (lpDragHeader == NULL)
                return DROPEFFECT_NONE;

            // dragged column must originate from this control
            if (*((HWND*)lpDragHeader) == m_hWnd)
                dwEffect = DropColumn(point) ? DROPEFFECT_MOVE : DROPEFFECT_NONE;

            GlobalUnlock(StgMedium.hGlobal);
        }

        return dwEffect;
    }

    BOOL OnDrop(FORMATETC& FormatEtc, STGMEDIUM& StgMedium, DWORD dwEffect, CPoint point)
    {
        if (FormatEtc.cfFormat == m_nHeaderClipboardFormat) {
            if (m_nDragColumn != NULL_COLUMN && m_nHotDivider != NULL_COLUMN) {
                CListColumn listColumn;

                if (!GetColumn(m_nDragColumn, listColumn))
                    return FALSE;

                // move column to new position
                m_aColumns.RemoveAt(m_nDragColumn);
                m_aColumns.InsertAt((m_nDragColumn < m_nHotColumn ? (m_nHotDivider == 0 ? 0 : m_nHotDivider - 1) : m_nHotDivider), listColumn);
                Invalidate();
            }

            return TRUE;
        }

        // not supported
        return FALSE;
    }

    void OnDragLeave()
    {
    }

    BOOL OnRenderData(FORMATETC& FormatEtc, STGMEDIUM *pStgMedium, BOOL bDropComplete)
    {
        if (FormatEtc.cfFormat == m_nHeaderClipboardFormat) {
            pStgMedium->tymed = TYMED_HGLOBAL;
            pStgMedium->hGlobal = GlobalAlloc(GMEM_MOVEABLE, sizeof(HWND));

            if (pStgMedium->hGlobal == NULL)
                return FALSE;

            LPBYTE lpDragHeader = (LPBYTE)GlobalLock(pStgMedium->hGlobal);

            if (lpDragHeader == NULL)
                return FALSE;

            // store this window handle
            *((HWND*)lpDragHeader) = m_hWnd;
            GlobalUnlock(pStgMedium->hGlobal);
            return TRUE;
        }

        return FALSE;
    }

    void DoPaint(CDCHandle dcPaint)
    {
        T* pT = static_cast<T*>(this);
        int nContextState = dcPaint.SaveDC();
        pT->DrawBkgnd(dcPaint);
        pT->DrawList(dcPaint);
        pT->DrawSelect(dcPaint);
        pT->DrawHeader(dcPaint);
        dcPaint.RestoreDC(nContextState);
    }

    void DrawBkgnd(CDCHandle dcPaint)
    {
        CRect rcClip;

        if (dcPaint.GetClipBox(rcClip) == ERROR)
            return;

        dcPaint.SetBkColor(m_rgbBackground);
        dcPaint.ExtTextOut(rcClip.left, rcClip.top, ETO_OPAQUE, rcClip, _T(""), 0, NULL);
        CRect rcClient;
        GetClientRect(rcClient);
        rcClient.top = (m_bShowHeader ? m_nHeaderHeight : 0);

        if (!m_bmpBackground.IsNull() && rcClip.bottom > rcClient.top) {
            CSize sizBackground;
            m_bmpBackground.GetSize(sizBackground);
            CDC dcBackgroundImage;
            dcBackgroundImage.CreateCompatibleDC(dcPaint);
            HBITMAP hOldBitmap = dcBackgroundImage.SelectBitmap(m_bmpBackground);

            if (m_bTileBackground) {
                // calculate tile image maximum rows and columns
                div_t divRows = div((int)rcClient.Height(), (int)sizBackground.cy);
                int nTileRows = divRows.rem > 0 ? divRows.quot + 1 : divRows.quot;
                div_t divColumns = div((int)rcClient.Width(), (int)sizBackground.cx);
                int nTileColumns = divColumns.rem > 0 ? divColumns.quot + 1 : divColumns.quot;

                // draw tiled background image
                for (int nRow = 0; nRow <= nTileRows; nRow++) {
                    for (int nColumn = 0; nColumn <= nTileColumns; nColumn++)
                        dcPaint.BitBlt(nColumn * sizBackground.cx, nRow * sizBackground.cy, sizBackground.cx, sizBackground.cy, dcBackgroundImage, 0, 0, SRCCOPY);
                }
            } else {
                CRect rcCentreImage(rcClient);

                // horizontally centre image if smaller than the client width
                if (sizBackground.cx < rcClient.Width()) {
                    rcCentreImage.left = (rcClient.Width() / 2) - (int)(sizBackground.cx / 2);
                    rcCentreImage.right = rcCentreImage.left + sizBackground.cx;
                }

                // vertically centre image if smaller than the client height
                if (sizBackground.cy + 16 < rcClient.Height()) {
                    rcCentreImage.top = (rcClient.Height() / 2) - (int)((sizBackground.cy + 16) / 2);
                    rcCentreImage.bottom = rcCentreImage.top + sizBackground.cy;
                }

                // draw centred background image
                dcPaint.BitBlt(rcCentreImage.left, rcCentreImage.top, rcCentreImage.Width(), rcCentreImage.Height(), dcBackgroundImage, 0, 0, SRCCOPY);
            }

            dcBackgroundImage.SelectBitmap(hOldBitmap);
        }
    }

    void DrawHeader(CDCHandle dcPaint)
    {
        if (!m_bShowHeader)
            return;

        CRect rcClip;

        if (dcPaint.GetClipBox(rcClip) == ERROR)
            return;

        CRect rcHeader;
        GetClientRect(rcHeader);
        rcHeader.bottom = m_nHeaderHeight;

        if (rcClip.top > rcHeader.bottom)
            return;

        if (!m_bShowThemed || m_thmHeader.IsThemeNull()) {
            dcPaint.SetBkColor(m_rgbHeaderBackground);
            dcPaint.ExtTextOut(rcHeader.left, rcHeader.top, ETO_OPAQUE, rcHeader, _T(""), 0, NULL);
        }

        CPen penHighlight;
        penHighlight.CreatePen(PS_SOLID, 1, m_rgbHeaderBorder);
        CPen penShadow;
        penShadow.CreatePen(PS_SOLID, 1, m_rgbHeaderShadow);
        CRect rcHeaderItem(rcHeader);
        rcHeaderItem.OffsetRect(-GetScrollPos(SB_HORZ), 0);
        int nHeaderWidth = 0;

        for (int nColumn = 0, nColumnCount = GetColumnCount(); nColumn < nColumnCount; rcHeaderItem.left = rcHeaderItem.right, nColumn++) {
            CListColumn listColumn;

            if (!GetColumn(nColumn, listColumn))
                break;

            rcHeaderItem.right = rcHeaderItem.left + listColumn.m_nWidth;
            nHeaderWidth += rcHeaderItem.Width();

            if (rcHeaderItem.right < rcClip.left)
                continue;

            if (rcHeaderItem.left > rcClip.right)
                break;

            // draw header and divider
            if (m_bShowThemed && !m_thmHeader.IsThemeNull())
                m_thmHeader.DrawThemeBackground(dcPaint, HP_HEADERITEM, nColumn == m_nHighlightColumn ? HIS_PRESSED : (nColumn == m_nHotColumn ? HIS_HOT : HIS_NORMAL), rcHeaderItem, NULL);
            else {
                if (nColumn == m_nHighlightColumn) {
                    dcPaint.SetBkColor(m_rgbHeaderHighlight);
                    dcPaint.ExtTextOut(rcHeaderItem.left, rcHeaderItem.top, ETO_OPAQUE, rcHeaderItem, _T(""), 0, NULL);
                }

                dcPaint.SelectPen(penShadow);
                dcPaint.MoveTo(rcHeaderItem.right - 1, rcHeaderItem.top + 4);
                dcPaint.LineTo(rcHeaderItem.right - 1, m_nHeaderHeight - 4);
                dcPaint.SelectPen(penHighlight);
                dcPaint.MoveTo(rcHeaderItem.right, rcHeaderItem.top + 4);
                dcPaint.LineTo(rcHeaderItem.right, m_nHeaderHeight - 4);
            }

            CRect rcHeaderText(rcHeaderItem);
            rcHeaderText.left += nColumn == 0 ? 4 : 3;
            rcHeaderText.OffsetRect(0, 1);
            BOOL bShowArrow = m_bShowSort && (rcHeaderItem.Width() > 15);

            // offset text bounding rectangle to account for sorting arrow
            if (bShowArrow && !listColumn.m_bFixed && listColumn.m_nIndex == m_nSortColumn)
                rcHeaderText.right -= 15;

            // margin header text
            rcHeaderText.DeflateRect(4, 0, 5, 0);

            // has this header item an associated image?
            if (listColumn.m_nImage != ITEM_IMAGE_NONE) {
                CSize sizeIcon;
                m_ilListItems.GetIconSize(sizeIcon);
                CRect rcHeaderImage;
                rcHeaderImage.left = listColumn.m_strText.IsEmpty() ? ((rcHeaderText.left + rcHeaderText.right) / 2) - (sizeIcon.cx / 2) - ((!m_bShowThemed || m_thmHeader.IsThemeNull()) ? 0 : 1) : rcHeaderText.left;
                rcHeaderImage.right = min(rcHeaderImage.left + sizeIcon.cx, rcHeaderItem.right - 2);
                rcHeaderImage.top = ((rcHeaderItem.top + rcHeaderItem.bottom) / 2) - (sizeIcon.cy / 2);
                rcHeaderImage.bottom = min(rcHeaderImage.top + sizeIcon.cy, rcHeaderItem.bottom);
                m_ilListItems.DrawEx(listColumn.m_nImage, dcPaint, rcHeaderImage, CLR_DEFAULT, CLR_DEFAULT, ILD_TRANSPARENT);
                // offset header text (for image)
                rcHeaderText.left += sizeIcon.cx + 4;
            }

            dcPaint.SelectFont(m_fntListFont);
            dcPaint.SetTextColor(m_rgbHeaderText);
            dcPaint.SetBkMode(TRANSPARENT);
            UINT nFormat = DT_SINGLELINE | DT_NOPREFIX | DT_VCENTER | DT_END_ELLIPSIS;

            if (listColumn.m_nFlags & ITEM_FLAGS_CENTRE)
                nFormat |= DT_CENTER;
            else if (listColumn.m_nFlags & ITEM_FLAGS_RIGHT)
                nFormat |= DT_RIGHT;
            else
                nFormat |= DT_LEFT;

            // draw header text
            if (!rcHeaderText.IsRectEmpty() && !listColumn.m_strText.IsEmpty())
                dcPaint.DrawText(listColumn.m_strText, listColumn.m_strText.GetLength(), rcHeaderText, nFormat);

            // draw sorting arrow
            if (bShowArrow && !listColumn.m_bFixed && listColumn.m_nIndex == m_nSortColumn) {
                CSize sizeIcon;
                m_ilListItems.GetIconSize(sizeIcon);
                CRect rcSortArrow;
                rcSortArrow.left = rcHeaderText.right + 4;
                rcSortArrow.right = min(rcSortArrow.left + sizeIcon.cx, rcHeaderItem.right);
                rcSortArrow.top = rcHeaderItem.Height() / 2 - 3;
                rcSortArrow.bottom = min(rcSortArrow.top + sizeIcon.cy, rcHeaderItem.bottom);
                m_ilListItems.DrawEx(m_bSortAscending ? ITEM_IMAGE_UP : ITEM_IMAGE_DOWN, dcPaint, rcSortArrow, CLR_DEFAULT, CLR_DEFAULT, ILD_TRANSPARENT);
            }
        }

        // draw remaining blank header
        if (m_bShowThemed && !m_thmHeader.IsThemeNull() && nHeaderWidth < rcHeader.Width())
            m_thmHeader.DrawThemeBackground(dcPaint, HP_HEADERITEM, HIS_NORMAL, CRect(nHeaderWidth, rcHeader.top, rcHeader.right + 2, rcHeader.bottom), NULL);

        // draw a frame around all header columns
        if ((!m_bShowThemed || m_thmHeader.IsThemeNull()) && nHeaderWidth > 0)
            dcPaint.Draw3dRect(CRect(rcHeader.left, rcHeader.top, rcHeader.right + 2, rcHeader.bottom), m_rgbHeaderBorder, m_rgbHeaderShadow);
    }

    void DrawRoundRect(CDCHandle dcPaint, CRect& rcRect, COLORREF rgbOuter, COLORREF rgbInner)
    {
        CRect rcRoundRect(rcRect);
        CPen penBorder;
        penBorder.CreatePen(PS_SOLID, 1, rgbOuter);
        CBrush bshInterior;
        bshInterior.CreateSolidBrush(m_rgbBackground);
        dcPaint.SelectPen(penBorder);
        dcPaint.SelectBrush(bshInterior);
        dcPaint.RoundRect(rcRoundRect, CPoint(5, 5));
        rcRoundRect.DeflateRect(1, 1);
        CPen penInnerBorder;
        penInnerBorder.CreatePen(PS_SOLID, 1, rgbInner);
        dcPaint.SelectPen(penInnerBorder);
        dcPaint.RoundRect(rcRoundRect, CPoint(2, 2));
    }

    void DrawGradient(CDCHandle dcPaint, CRect& rcRect, COLORREF rgbTop, COLORREF rgbBottom)
    {
        GRADIENT_RECT grdRect = { 0, 1 };
        TRIVERTEX triVertext[ 2 ] = {
            rcRect.left,
            rcRect.top,
            GetRValue(rgbTop) << 8,
            GetGValue(rgbTop) << 8,
            GetBValue(rgbTop) << 8,
            0x0000,
            rcRect.right,
            rcRect.bottom,
            GetRValue(rgbBottom) << 8,
            GetGValue(rgbBottom) << 8,
            GetBValue(rgbBottom) << 8,
            0x0000
        };
        dcPaint.GradientFill(triVertext, 2, &grdRect, 1, GRADIENT_FILL_RECT_V);
    }

    void DrawList(CDCHandle dcPaint)
    {
        T* pT = static_cast<T*>(this);
        CRect rcClip;

        if (dcPaint.GetClipBox(rcClip) == ERROR)
            return;

        CRect rcItem;
        rcItem.left = -GetScrollPos(SB_HORZ);
        rcItem.right = GetTotalWidth();
        rcItem.top = (m_bShowHeader ? m_nHeaderHeight : 0);
        rcItem.bottom = rcItem.top;

        // draw all visible items
        for (int nItem = GetTopItem(); nItem < pT->GetItemCount(); rcItem.top = rcItem.bottom, nItem++) {
            rcItem.bottom = rcItem.top + m_nItemHeight;

            if (rcItem.bottom < rcClip.top || rcItem.right < rcClip.left)
                continue;

            if (rcItem.top > rcClip.bottom || rcItem.left > rcClip.right)
                break;

            // may be implemented in a derived class
            pT->DrawItem(dcPaint, nItem, rcItem);
        }
    }

    void DrawItem(CDCHandle dcPaint, int nItem, CRect& rcItem)
    {
        T* pT = static_cast<T*>(this);
        CRect rcClip;

        if (dcPaint.GetClipBox(rcClip) == ERROR)
            return;

        int nFocusItem = NULL_ITEM;
        int nFocusSubItem = NULL_SUBITEM;
        GetFocusItem(nFocusItem, nFocusSubItem);
        BOOL bSelectedItem = IsSelected(nItem);
        BOOL bControlFocus = (GetFocus() == m_hWnd || m_bEditItem);

        // draw selected background
        if (bSelectedItem) {
            if (m_bShowThemed && !m_thmHeader.IsThemeNull()) {
                // draw select border
                DrawRoundRect(dcPaint, rcItem, bControlFocus ? m_rgbSelectOuter : m_rgbNoFocusOuter, bControlFocus ? m_rgbSelectInner : m_rgbNoFocusInner);
                CRect rcSelect(rcItem);
                rcSelect.DeflateRect(2, 2);
                // fill selected area
                DrawGradient(dcPaint, rcSelect, bControlFocus ? m_rgbSelectTop : m_rgbNoFocusTop, bControlFocus ? m_rgbSelectBottom : m_rgbNoFocusBottom);
            } else {
                dcPaint.SetBkColor(m_rgbSelectedItem);
                dcPaint.ExtTextOut(rcItem.left, rcItem.top, ETO_OPAQUE, rcItem, _T(""), 0, NULL);
            }
        }

        CRect rcSubItem(rcItem);
        rcSubItem.right = rcSubItem.left;

        for (int nSubItem = 0, nColumnCount = GetColumnCount(); nSubItem < nColumnCount; rcSubItem.left = rcSubItem.right + 1, nSubItem++) {
            CListColumn listColumn;

            if (!GetColumn(nSubItem, listColumn))
                break;

            rcSubItem.right = rcSubItem.left + listColumn.m_nWidth - 1;

            if (rcSubItem.right < rcClip.left || rcSubItem.Width() == 0)
                continue;

            if (rcSubItem.left > rcClip.right)
                break;

            CString strItemText = pT->GetItemText(nItem, listColumn.m_nIndex);
            int nItemImage = pT->GetItemImage(nItem, listColumn.m_nIndex);
            UINT nItemFormat = pT->GetItemFormat(nItem, listColumn.m_nIndex);
            UINT nItemFlags = pT->GetItemFlags(nItem, listColumn.m_nIndex);

            // custom draw subitem format
            if (nItemFormat == ITEM_FORMAT_CUSTOM) {
                pT->DrawCustomItem(dcPaint, nItem, nSubItem, rcSubItem);
                return;
            }

            BOOL bFocusSubItem = (m_bFocusSubItem && nFocusItem == nItem && nFocusSubItem == nSubItem);
            COLORREF rgbBackground = m_rgbBackground;
            COLORREF rgbText = m_rgbItemText;

            if (bFocusSubItem) {
                if (m_bShowThemed && !m_thmHeader.IsThemeNull()) {
                    // only draw subitem focus if control has focus
                    if (bControlFocus) {
                        // draw select border
                        DrawRoundRect(dcPaint, rcSubItem, bControlFocus ? m_rgbSelectOuter : m_rgbNoFocusOuter, bControlFocus ? m_rgbSelectInner : m_rgbNoFocusInner);
                        CRect rcSelect(rcSubItem);
                        rcSelect.DeflateRect(2, 2);

                        // fill selected area
                        if (!m_bEditItem)
                            DrawGradient(dcPaint, rcSelect, m_rgbFocusTop, m_rgbFocusBottom);
                        else {
                            dcPaint.SetBkColor(m_rgbBackground);
                            dcPaint.ExtTextOut(rcSelect.left, rcSelect.top, ETO_OPAQUE, rcSelect, _T(""), 0, NULL);
                        }
                    }
                } else {
                    dcPaint.SetBkColor(m_bEditItem ? m_rgbBackground : m_rgbItemFocus);
                    dcPaint.ExtTextOut(rcSubItem.left, rcSubItem.top, ETO_OPAQUE, rcSubItem, _T(""), 0, NULL);

                    if (m_bEditItem) {
                        CBrush bshSelectFrame;
                        bshSelectFrame.CreateSolidBrush(m_rgbItemFocus);
                        dcPaint.FrameRect(rcSubItem, bshSelectFrame);
                    }
                }
            } else if (pT->GetItemColours(nItem, nSubItem, rgbBackground, rgbText) && rgbBackground != m_rgbBackground) {
                CPen penBorder;
                penBorder.CreatePen(PS_SOLID, 1, rgbBackground);
                CBrush bshInterior;
                bshInterior.CreateSolidBrush(rgbBackground);
                dcPaint.SelectPen(penBorder);
                dcPaint.SelectBrush(bshInterior);
                dcPaint.RoundRect(rcSubItem, CPoint(3, 3));
            }

            CRect rcItemText(rcSubItem);
            // margin item text
            rcItemText.left += nSubItem == 0 ? 4 : 3;
            rcItemText.DeflateRect(4, 0);

            // draw subitem image if supplied
            if (!m_ilItemImages.IsNull() && nItemImage != ITEM_IMAGE_NONE && (!m_bEditItem || (m_bEditItem && !bFocusSubItem))) {
                CSize sizeIcon;
                m_ilItemImages.GetIconSize(sizeIcon);
                CRect rcItemImage;
                rcItemImage.left = strItemText.IsEmpty() ? ((rcItemText.left + rcItemText.right) / 2) - (sizeIcon.cx / 2) - ((!m_bShowThemed || m_thmHeader.IsThemeNull()) ? 0 : 1) : rcItemText.left;
                rcItemImage.right = min(rcItemImage.left + sizeIcon.cx, rcSubItem.right);
                rcItemImage.top = ((rcSubItem.top + rcSubItem.bottom) / 2) - (sizeIcon.cy / 2);
                rcItemImage.bottom = min(rcItemImage.top + sizeIcon.cy, rcSubItem.bottom);
                m_ilItemImages.DrawEx(nItemImage, dcPaint, rcItemImage, CLR_DEFAULT, CLR_DEFAULT, ILD_TRANSPARENT);
                // offset item text (for image)
                rcItemText.left += sizeIcon.cx + 4;
            }

            if (rcItemText.IsRectEmpty())
                continue;

            dcPaint.SelectFont(pT->GetItemFont(nItem, nSubItem));
            dcPaint.SetTextColor(((!m_bShowThemed || m_thmHeader.IsThemeNull()) && bSelectedItem && !bFocusSubItem) ? m_rgbSelectedText : rgbText);
            dcPaint.SetBkMode(TRANSPARENT);
            UINT nFormat = DT_SINGLELINE | DT_NOPREFIX | DT_VCENTER | DT_END_ELLIPSIS;

            if (nItemFlags & ITEM_FLAGS_CENTRE)
                nFormat |= DT_CENTER;
            else if (nItemFlags & ITEM_FLAGS_RIGHT)
                nFormat |= DT_RIGHT;
            else
                nFormat |= DT_LEFT;

            switch (nItemFormat) {
            case ITEM_FORMAT_DATETIME:
                if (!strItemText.IsEmpty()) {
                    SYSTEMTIME stItemDate;

                    if (!GetItemDate(nItem, listColumn.m_nIndex, stItemDate))
                        break;

                    CString strItemDate;

                    if (nItemFlags & ITEM_FLAGS_DATE_ONLY)
                        strItemDate = FormatDate(stItemDate);
                    else if (nItemFlags & ITEM_FLAGS_TIME_ONLY)
                        strItemDate = FormatTime(stItemDate);
                    else
                        strItemDate = FormatDate(stItemDate) + _T(" ") + FormatTime(stItemDate);

                    dcPaint.DrawText(strItemDate, strItemDate.GetLength(), rcItemText, nFormat);
                }

                break;

            case ITEM_FORMAT_CHECKBOX:
            case ITEM_FORMAT_CHECKBOX_3STATE:   {
                CSize sizeIcon;
                m_ilListItems.GetIconSize(sizeIcon);
                CRect rcCheckBox;
                rcCheckBox.left = ((rcItemText.left + rcItemText.right) / 2) - (sizeIcon.cx / 2) - 1;
                rcCheckBox.right = min(rcCheckBox.left + sizeIcon.cx, rcSubItem.right);
                rcCheckBox.top = ((rcSubItem.top + rcSubItem.bottom) / 2) - (sizeIcon.cy / 2);
                rcCheckBox.bottom = min(rcCheckBox.top + sizeIcon.cy, rcSubItem.bottom);
                int nCheckValue = _ttoi(strItemText);

                if (nItemFormat == ITEM_FORMAT_CHECKBOX)
                    m_ilListItems.DrawEx(nCheckValue > 0 ? ITEM_IMAGE_CHECK_ON : ITEM_IMAGE_CHECK_OFF, dcPaint, rcCheckBox, CLR_DEFAULT, CLR_DEFAULT, ILD_TRANSPARENT);
                else {
                    int nCheckImage = ITEM_IMAGE_3STATE_UNDEF;

                    if (nCheckValue < 0)
                        nCheckImage = ITEM_IMAGE_3STATE_OFF;
                    else if (nCheckValue > 0)
                        nCheckImage = ITEM_IMAGE_3STATE_ON;

                    m_ilListItems.DrawEx(nCheckImage, dcPaint, rcCheckBox, CLR_DEFAULT, CLR_DEFAULT, ILD_TRANSPARENT);
                }
            }
            break;

            case ITEM_FORMAT_PROGRESS:
                if (m_bShowThemed && !m_thmProgress.IsThemeNull() && !(nItemFlags & ITEM_FLAGS_PROGRESS_SOLID)) {
                    // draw progress frame
                    CRect rcProgress(rcSubItem);
                    rcProgress.DeflateRect(3, 2);
                    m_thmProgress.DrawThemeBackground(dcPaint, PP_BAR, 0, rcProgress, NULL);
                    // draw progress bar
                    rcProgress.DeflateRect(3, 3, 2, 3);
                    rcProgress.right = rcProgress.left + (int)((double)rcProgress.Width() * ((max(min(_tstof(strItemText), 100), 0)) / 100.0));
                    m_thmProgress.DrawThemeBackground(dcPaint, PP_CHUNK, 0, rcProgress, NULL);
                } else {
                    CRect rcProgress(rcSubItem);
                    rcProgress.DeflateRect(3, 2);
                    // draw progress border
                    DrawRoundRect(dcPaint, rcProgress, m_rgbHeaderShadow, m_rgbHeaderBackground);
                    // fill progress bar area
                    rcProgress.DeflateRect(3, 3);
                    rcProgress.right = rcProgress.left + (int)((double)rcProgress.Width() * ((max(min(_tstof(strItemText), 100), 0)) / 100.0));
                    DrawGradient(dcPaint, rcProgress, m_rgbProgressTop, m_rgbProgressBottom);
                }

                break;

            case ITEM_FORMAT_HYPERLINK:
                if (nItem == m_nHotItem && nSubItem == m_nHotSubItem && !(nItemFlags & ITEM_FLAGS_READ_ONLY)) {
                    dcPaint.SelectFont(m_fntUnderlineFont);
                    dcPaint.SetTextColor(m_rgbHyperLink);
                }

            default:                            // draw item text
                if (!strItemText.IsEmpty())
                    dcPaint.DrawText(strItemText, strItemText.GetLength(), rcItemText, nFormat);

                break;
            }
        }
    }

    void DrawSelect(CDCHandle dcPaint)
    {
        if (!m_bGroupSelect)
            return;

        int nHorzScroll = GetScrollPos(SB_HORZ);
        int nVertScroll = GetScrollPos(SB_VERT);
        CRect rcGroupSelect(m_rcGroupSelect);
        rcGroupSelect.OffsetRect(-nHorzScroll, -nVertScroll);
        CRect rcClient;
        GetClientRect(rcClient);
        rcClient.top = (m_bShowHeader ? m_nHeaderHeight : 0);

        // limit box to list client area if scrolled to limits
        if (nHorzScroll > (GetTotalWidth() - rcClient.Width()))
            rcGroupSelect.right = min(rcClient.right, rcGroupSelect.right);

        if (nHorzScroll == 0)
            rcGroupSelect.left = max(rcClient.left, rcGroupSelect.left);

        if (nVertScroll > (GetTotalHeight() - rcClient.Height()))
            rcGroupSelect.bottom = min(rcClient.bottom, rcGroupSelect.bottom);

        if (nVertScroll == 0)
            rcGroupSelect.top = max(rcClient.top, rcGroupSelect.top);

        // limit bitmap to client area
        CRect rcSelectArea(rcGroupSelect);
        rcSelectArea.IntersectRect(rcSelectArea, rcClient);
        CDC dcBackground;
        dcBackground.CreateCompatibleDC(dcPaint);
        int nBackgroundContext = dcBackground.SaveDC();
        CBitmap bmpBackground;
        bmpBackground.CreateCompatibleBitmap(dcPaint, rcSelectArea.Width(), rcSelectArea.Height());
        dcBackground.SelectBitmap(bmpBackground);
        // take a copy of existing backgroud
        dcBackground.BitBlt(0, 0, rcSelectArea.Width(), rcSelectArea.Height(), dcPaint, rcSelectArea.left, rcSelectArea.top, SRCCOPY);
        CDC dcGroupSelect;
        dcGroupSelect.CreateCompatibleDC(dcPaint);
        int nGroupSelectContext = dcGroupSelect.SaveDC();
        CBitmap bmpGroupSelect;
        bmpGroupSelect.CreateCompatibleBitmap(dcPaint, rcSelectArea.Width(), rcSelectArea.Height());
        dcGroupSelect.SelectBitmap(bmpGroupSelect);
        // draw group select box
        dcGroupSelect.SetBkColor((!m_bShowThemed || m_thmHeader.IsThemeNull()) ? m_rgbItemFocus : m_rgbSelectedItem);
        dcGroupSelect.ExtTextOut(0, 0, ETO_OPAQUE, CRect(CPoint(0), rcSelectArea.Size()), _T(""), 0, NULL);
        BLENDFUNCTION blendFunction;
        blendFunction.BlendOp = AC_SRC_OVER;
        blendFunction.BlendFlags = 0;
        blendFunction.SourceConstantAlpha = 180;
        blendFunction.AlphaFormat = 0;
        // blend existing background with selection box
        dcGroupSelect.AlphaBlend(0, 0, rcSelectArea.Width(), rcSelectArea.Height(), dcBackground, 0, 0, rcSelectArea.Width(), rcSelectArea.Height(), blendFunction);
        // draw blended selection box
        dcPaint.BitBlt(rcSelectArea.left, rcSelectArea.top, rcSelectArea.Width(), rcSelectArea.Height(), dcGroupSelect, 0, 0, SRCCOPY);
        // draw selection box frame
        CBrush bshSelectFrame;
        bshSelectFrame.CreateSolidBrush((!m_bShowThemed || m_thmHeader.IsThemeNull()) ? m_rgbItemText : m_rgbSelectedItem);
        dcPaint.FrameRect(rcGroupSelect, bshSelectFrame);
        dcBackground.RestoreDC(nBackgroundContext);
        dcGroupSelect.RestoreDC(nGroupSelectContext);
    }

    void DrawCustomItem(CDCHandle dcPaint, int nItem, int nSubItem, CRect& rcSubItem)
    {
        ATLASSERT(FALSE);   // must be implemented in a derived class
    }
};

struct CSubItem {
    CString m_strText;
    int m_nImage;
    UINT m_nFormat;
    UINT m_nFlags;
    CListArray < CString > m_aComboList;
    HFONT m_hFont;
    COLORREF m_rgbBackground;
    COLORREF m_rgbText;
};

template < class TData = DWORD >
struct CListItem {
    CListArray < CSubItem > m_aSubItems;
    CString m_strToolTip;
    TData m_tData;
};

template < class TData >
class CListCtrlData : public CListImpl< CListCtrlData< TData > >
{
public:
    DECLARE_WND_CLASS(_T("ListCtrl"))

protected:
    CListArray < CListItem< TData > > m_aItems;

public:
    int AddItem(CListItem< TData >& listItem)
    {
        if (!m_aItems.Add(listItem))
            return -1;

        return CListImpl< CListCtrlData >::AddItem() ? GetItemCount() - 1 : -1;
    }

    int AddItem(LPCTSTR lpszText, int nImage = ITEM_IMAGE_NONE, UINT nFormat = ITEM_FORMAT_NONE, UINT nFlags = ITEM_FLAGS_NONE)
    {
        CSubItem listSubItem;
        listSubItem.m_nImage = ITEM_IMAGE_NONE;
        listSubItem.m_nFormat = nFormat;
        listSubItem.m_nFlags = ValidateFlags(nFlags);
        listSubItem.m_hFont = NULL;
        listSubItem.m_rgbBackground = m_rgbBackground;
        listSubItem.m_rgbText = m_rgbItemText;
        CListItem< TData > listItem;

        for (int nSubItem = 0; nSubItem < GetColumnCount(); nSubItem++)
            listItem.m_aSubItems.Add(listSubItem);

        // set item details for first subitem
        listItem.m_aSubItems[ 0 ].m_strText = lpszText;
        listItem.m_aSubItems[ 0 ].m_nImage = nImage;
        return AddItem(listItem);
    }

    BOOL DeleteItem(int nItem)
    {
        if (nItem < 0 || nItem >= GetItemCount())
            return FALSE;

        return m_aItems.RemoveAt(nItem) ? CListImpl< CListCtrlData >::DeleteItem(nItem) : FALSE;
    }

    BOOL DeleteAllItems()
    {
        m_aItems.RemoveAll();
        return CListImpl< CListCtrlData >::DeleteAllItems();
    }

    int GetItemCount()
    {
        return m_aItems.GetSize();
    }

    BOOL GetItem(int nItem, CListItem< TData >& listItem)
    {
        if (nItem < 0 || nItem >= GetItemCount())
            return FALSE;

        listItem = m_aItems[ nItem ];
        return TRUE;
    }

    BOOL GetSubItem(int nItem, int nSubItem, CSubItem& listSubItem)
    {
        CListItem< TData > listItem;

        if (!GetItem(nItem, listItem))
            return FALSE;

        if (nSubItem < 0 || nSubItem >= (int)listItem.m_aSubItems.GetSize())
            return FALSE;

        listSubItem = listItem.m_aSubItems[ nSubItem ];
        return TRUE;
    }

    CString GetItemText(int nItem, int nSubItem)
    {
        CSubItem listSubItem;
        return GetSubItem(nItem, nSubItem, listSubItem) ? listSubItem.m_strText : _T("");
    }

    int GetItemImage(int nItem, int nSubItem)
    {
        CSubItem listSubItem;
        return GetSubItem(nItem, nSubItem, listSubItem) ? listSubItem.m_nImage : ITEM_IMAGE_NONE;
    }

    UINT GetItemFormat(int nItem, int nSubItem)
    {
        CSubItem listSubItem;

        if (!GetSubItem(nItem, nSubItem, listSubItem))
            return FALSE;

        return listSubItem.m_nFormat == ITEM_FORMAT_NONE ? GetColumnFormat(IndexToOrder(nSubItem)) : listSubItem.m_nFormat;
    }

    UINT GetItemFlags(int nItem, int nSubItem)
    {
        CSubItem listSubItem;

        if (!GetSubItem(nItem, nSubItem, listSubItem))
            return FALSE;

        return listSubItem.m_nFlags == ITEM_FLAGS_NONE ? GetColumnFlags(IndexToOrder(nSubItem)) : listSubItem.m_nFlags;
    }

    BOOL GetItemComboList(int nItem, int nSubItem, CListArray < CString >& aComboList)
    {
        CSubItem listSubItem;

        if (!GetSubItem(nItem, nSubItem, listSubItem))
            return FALSE;

        aComboList = listSubItem.m_aComboList;
        return aComboList.IsEmpty() ? GetColumnComboList(IndexToOrder(nSubItem), aComboList) : !aComboList.IsEmpty();
    }

    HFONT GetItemFont(int nItem, int nSubItem)
    {
        CSubItem listSubItem;

        if (!GetSubItem(nItem, nSubItem, listSubItem))
            return FALSE;

        return listSubItem.m_hFont == NULL ? CListImpl< CListCtrlData >::GetItemFont(nItem, nSubItem) : listSubItem.m_hFont;
    }

    BOOL GetItemColours(int nItem, int nSubItem, COLORREF& rgbBackground, COLORREF& rgbText)
    {
        CSubItem listSubItem;

        if (!GetSubItem(nItem, nSubItem, listSubItem))
            return FALSE;

        rgbBackground = listSubItem.m_rgbBackground;
        rgbText = listSubItem.m_rgbText;
        return TRUE;
    }

    CString GetItemToolTip(int nItem, int nSubItem)
    {
        CListItem< TData > listItem;
        return GetItem(nItem, listItem) ? listItem.m_strToolTip : _T("");
    }

    BOOL GetItemData(int nItem, TData& tData)
    {
        CListItem< TData > listItem;

        if (!GetItem(nItem, listItem))
            return FALSE;

        tData = listItem.m_tData;
        return TRUE;
    }

    BOOL SetItemText(int nItem, int nSubItem, LPCTSTR lpszText)
    {
        if (nItem < 0 || nItem >= GetItemCount())
            return FALSE;

        if (nSubItem < 0 || nSubItem >= (int)m_aItems[ nItem ].m_aSubItems.GetSize())
            return FALSE;

        m_aItems[ nItem ].m_aSubItems[ nSubItem ].m_strText = lpszText;
        return TRUE;
    }

    BOOL SetItemComboIndex(int nItem, int nSubItem, int nIndex)
    {
        CListArray < CString > aComboList;

        if (!GetItemComboList(nItem, nSubItem, aComboList))
            return FALSE;

        return SetItemText(nItem, nSubItem, nIndex < 0 || nIndex >= aComboList.GetSize() ? _T("") : aComboList[ nIndex ]);
    }

    BOOL SetItemImage(int nItem, int nSubItem, int nImage)
    {
        if (nItem < 0 || nItem >= GetItemCount())
            return FALSE;

        if (nSubItem < 0 || nSubItem >= (int)m_aItems[ nItem ].m_aSubItems.GetSize())
            return FALSE;

        m_aItems[ nItem ].m_aSubItems[ nSubItem ].m_nImage = nImage;
        return TRUE;
    }

    BOOL SetItemFormat(int nItem, int nSubItem, UINT nFormat, UINT nFlags = ITEM_FLAGS_NONE)
    {
        if (nItem < 0 || nItem >= GetItemCount())
            return FALSE;

        if (nSubItem < 0 || nSubItem >= (int)m_aItems[ nItem ].m_aSubItems.GetSize())
            return FALSE;

        m_aItems[ nItem ].m_aSubItems[ nSubItem ].m_nFormat = nFormat;
        m_aItems[ nItem ].m_aSubItems[ nSubItem ].m_nFlags = nFlags;
        return TRUE;
    }

    BOOL SetItemFormat(int nItem, int nSubItem, UINT nFormat, UINT nFlags, CListArray < CString >& aComboList)
    {
        if (nItem < 0 || nItem >= GetItemCount())
            return FALSE;

        if (nSubItem < 0 || nSubItem >= (int)m_aItems[ nItem ].m_aSubItems.GetSize())
            return FALSE;

        m_aItems[ nItem ].m_aSubItems[ nSubItem ].m_nFormat = nFormat;
        m_aItems[ nItem ].m_aSubItems[ nSubItem ].m_nFlags = nFlags;
        m_aItems[ nItem ].m_aSubItems[ nSubItem ].m_aComboList = aComboList;
        return TRUE;
    }

    BOOL SetItemFont(int nItem, int nSubItem, HFONT hFont)
    {
        if (nItem < 0 || nItem >= GetItemCount())
            return FALSE;

        if (nSubItem < 0 || nSubItem >= (int)m_aItems[ nItem ].m_aSubItems.GetSize())
            return FALSE;

        m_aItems[ nItem ].m_aSubItems[ nSubItem ].m_hFont = hFont;
        return TRUE;
    }

    BOOL SetItemColours(int nItem, int nSubItem, COLORREF rgbBackground, COLORREF rgbText)
    {
        if (nItem < 0 || nItem >= GetItemCount())
            return FALSE;

        if (nSubItem < 0 || nSubItem >= (int)m_aItems[ nItem ].m_aSubItems.GetSize())
            return FALSE;

        m_aItems[ nItem ].m_aSubItems[ nSubItem ].m_rgbBackground = rgbBackground;
        m_aItems[ nItem ].m_aSubItems[ nSubItem ].m_rgbText = rgbText;
        return TRUE;
    }

    void ReverseItems()
    {
        m_aItems.Reverse();
    }

    class CompareItem
    {
    public:
        CompareItem(int nColumn) : m_nColumn(nColumn) {}
        inline bool operator()(const CListItem< TData >& listItem1, const CListItem< TData >& listItem2)
        {
            return (listItem1.m_aSubItems[ m_nColumn ].m_strText.Compare(listItem2.m_aSubItems[ m_nColumn ].m_strText) < 0);
        }

    protected:
        int m_nColumn;
    };

    void SortItems(int nColumn, BOOL bAscending)
    {
        m_aItems.Sort(CompareItem(nColumn));
    }

    BOOL SetItemToolTip(int nItem, LPCTSTR lpszToolTip)
    {
        if (nItem < 0 || nItem >= GetItemCount())
            return FALSE;

        m_aItems[ nItem ].m_strToolTip = lpszToolTip;
        return TRUE;
    }

    BOOL SetItemData(int nItem, TData& tData)
    {
        if (nItem < 0 || nItem >= GetItemCount())
            return FALSE;

        m_aItems[ nItem ].m_tData = tData;
        return TRUE;
    }
};

typedef CListCtrlData< DWORD > CListCtrl;
